R

ReflexKR

Reflex Docs

The Component Workshop: Reflex's 100 Library Components

·

Course overview

Layout

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

Typography

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

Forms

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

Data display

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

Disclosure

·

Tabs

·

Accordion

·

Segmented Control

Overlay

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

Dynamic rendering

·

Cond

·

Match

·

Foreach

·

Auto Scroll

Media

·

Image

·

Audio

·

Video

Tables and data grids

·

Table

·

Data Table

·

Data Editor

Charts

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

Chart parts

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

Other chart libraries

·

Plotly

·

Pyplot

HTML elements

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

Other tools

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

The Component Workshop: Reflex's 100 Library Components
Disclosure

Tabs

rx.tabs.rootrx.tabs.listrx.tabs.triggerrx.tabs.content

Official docs

Workshop order

On the product page, split 'Description', 'Specs' and 'Reviews' into tabs so only one shows at a time.

Tabs stack related groups of content at the same level and show one at a time. They are built from four parts.

  • rx.tabs.root: wraps everything and remembers which tab is open.
  • rx.tabs.list: the row where the tab buttons sit.
  • rx.tabs.trigger: a button that opens its tab when clicked. Its value is its name tag.
  • rx.tabs.content: the tab's content, shown when the trigger with the same value is clicked.

item on tab 1

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Tab 1", value="tab1"),
        rx.tabs.trigger("Tab 2", value="tab2"),
    ),
    rx.tabs.content(rx.text("item on tab 1"), value="tab1"),
    rx.tabs.content(rx.text("item on tab 2"), value="tab2"),
    default_value="tab1",
)

Try the props

orientation and activation_mode go on root, size and loop on list, and color_scheme and disabled on trigger. Click a tab, then move between tabs with the arrow keys.

Try the props

Make changes to your account

orientation

activation_mode

size

loop

color

disable_settings

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Account", value="account", color_scheme="indigo"),
        rx.tabs.trigger("Documents", value="documents", color_scheme="indigo"),
        rx.tabs.trigger("Settings", value="settings", color_scheme="indigo", disabled=False),
        size="2",
        loop=True,
    ),
    rx.box(
        rx.tabs.content(rx.text("Make changes to your account"), value="account"),
        rx.tabs.content(rx.text("Update your documents"), value="documents"),
        rx.tabs.content(rx.text("Edit your personal profile"), value="settings"),
        padding="12px",
    ),
    default_value="account",
    orientation="horizontal",
    activation_mode="automatic",
)
With activation_mode set to manual, the arrow keys only move focus; you press Enter or Space to open a tab. Turn loop off and the arrow keys stop at the ends.
PartPropValuesUse
rootdefault_valueA trigger's valueTab open at first (uncontrolled)
rootvalue + on_changeState value, eventManage the open tab with State (controlled)
rootorientation"horizontal" (default) "vertical"Direction of the tab row and the arrow keys
rootactivation_mode"automatic" (default) "manual"Open on focus, or only when pressed
listsize"1" "2" (default)Tab button size
listloopTrue (default) / FalseWhether the arrow keys wrap around at the end
triggervalueString (unique)This tab's name tag
triggerdisabledTrue / FalseMake it unclickable
triggercolor_schemeRadix color nameUnderline color when open
contentvalueSame string as the triggerWhich tab this content belongs to
contentforce_mountTrue / FalseKeep it on the page even when closed

The tab open at first

Give default_value a trigger's value and that tab is open from the start. Without it, no tab is open at first, so you'll almost always want to set it.

item on tab 2

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Tab 1", value="tab1"),
        rx.tabs.trigger("Tab 2", value="tab2"),
    ),
    rx.tabs.content(rx.text("item on tab 1"), value="tab1"),
    rx.tabs.content(rx.text("item on tab 2"), value="tab2"),
    default_value="tab2",
)

Vertical tabs

With orientation="vertical", the tab row stands vertically on the left. Not only the visual direction changes but the keyboard direction too: up and down arrows move between tabs (left and right when horizontal). For vertical tabs, give every trigger a matching content; if one is missing, the width of the tab row can jump around.

item on tab 1

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Tab 1", value="tab1"),
        rx.tabs.trigger("Tab 2", value="tab2"),
    ),
    rx.tabs.content(rx.text("item on tab 1"), value="tab1", padding_left="16px"),
    rx.tabs.content(rx.text("item on tab 2"), value="tab2", padding_left="16px"),
    default_value="tab1",
    orientation="vertical",
)

Managing the open tab with State (controlled)

Give value a State value and change it in on_change, and the server knows which tab is open. You can then open another tab from a button, or do other work when the tab changes. on_change passes the value of the newly opened tab.

tab1 selected (changed 0 times)

items on tab 1

class TabsPickState(rx.State):
    value: str = "tab1"
    changes: int = 0

    @rx.event
    def go_tab2(self):
        self.value = "tab2"

    @rx.event
    def change_tab(self, val: str):
        self.value = val
        self.changes += 1


rx.vstack(
    rx.text(TabsPickState.value, " selected (changed ", TabsPickState.changes, " times)"),
    rx.tabs.root(
        rx.tabs.list(
            rx.tabs.trigger("Tab 1", value="tab1"),
            rx.tabs.trigger("Tab 2", value="tab2"),
        ),
        rx.tabs.content(rx.text("items on tab 1"), value="tab1"),
        rx.tabs.content(rx.text("items on tab 2"), value="tab2"),
        value=TabsPickState.value,
        on_change=TabsPickState.change_tab,
        width="100%",
    ),
    rx.button("Jump to Tab 2", on_click=TabsPickState.go_tab2, size="1", variant="soft"),
)

Tab buttons (trigger)

Each trigger's value must be unique and pairs with the content of the same value. disabled=True blocks clicking, and color_scheme changes the color of the underline that appears when the tab is open. Triggers can hold components such as icons, too.

item on tab 1

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Tab 1", value="tab1"),
        rx.tabs.trigger("Tab 2", value="tab2", color_scheme="green"),
        rx.tabs.trigger("Tab 3", value="tab3", disabled=True),
    ),
    rx.tabs.content(rx.text("item on tab 1"), value="tab1"),
    rx.tabs.content(rx.text("item on tab 2"), value="tab2"),
    rx.tabs.content(rx.text("item on tab 3"), value="tab3"),
    default_value="tab1",
)

Tab content and force_mount

A closed tab's content is removed from the page. So anything that lived only in the browser, such as text typed into an input, disappears when you switch tabs. With force_mount=True, it stays in place even when closed. Closed tabs then stay visible too, so add a style that hides them when data-state='inactive'. It's also used when you drive the open and close animation yourself with an animation library.

rx.tabs.root(
    rx.tabs.list(
        rx.tabs.trigger("Write", value="write"),
        rx.tabs.trigger("Preview", value="preview"),
    ),
    rx.tabs.content(
        rx.el.input(placeholder="Type, then switch tabs", default_value="", padding="6px", margin_top="8px"),
        value="write",
        force_mount=True,
        style={"&[data-state='inactive']": {"display": "none"}},
    ),
    rx.tabs.content(rx.text("The input on the other tab keeps its text.", margin_top="8px"), value="preview"),
    default_value="write",
)

Filling the order

The tab buttons get icons, and the contents hold text, a data list and badges.

A light, waterproof jacket for everyday rain.

rx.card(
    rx.tabs.root(
        rx.tabs.list(
            rx.tabs.trigger(rx.icon("file-text", size=14), "Description", value="desc"),
            rx.tabs.trigger(rx.icon("ruler", size=14), "Specs", value="specs"),
            rx.tabs.trigger(rx.icon("star", size=14), "Reviews", value="reviews"),
        ),
        rx.tabs.content(
            rx.text("A light, waterproof jacket for everyday rain.", size="2"),
            value="desc",
            padding_top="12px",
        ),
        rx.tabs.content(
            rx.data_list.root(
                rx.data_list.item(rx.data_list.label("Weight"), rx.data_list.value("320 g")),
                rx.data_list.item(rx.data_list.label("Material"), rx.data_list.value("Nylon")),
            ),
            value="specs",
            padding_top="12px",
        ),
        rx.tabs.content(
            rx.hstack(rx.badge("4.8", color_scheme="amber"), rx.text("from 214 reviews", size="2")),
            value="reviews",
            padding_top="12px",
        ),
        default_value="desc",
    ),
    max_width="420px",
)

Workshop notes

  • tabs.root > tabs.list > tabs.trigger(value=…), paired with tabs.content(value=…) by value.

  • default_value for the tab open at first (uncontrolled); value + on_change to let State manage it (controlled).

  • orientation changes both the visual direction and the arrow-key direction. When vertical, give every trigger its content.

  • list: size · loop; trigger: disabled · color_scheme; content: force_mount.

← Previous

Data display · Moment

Next →

Disclosure · Accordion

Get news about new lessons

We'll email you when this course gets new lessons or a follow-up course opens.

We collect: your email address · Purpose: news about new lessons or the launch of this course · Kept until: you ask us to remove it. We never use it for anything else or share it. Reply to any notice email and we'll delete it right away.

Questions & answers

Ask anything about this lesson. Questions from every language version of this page appear together.

Shown as plain text, without links or formatting.

R

ReflexKR

Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.

ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee

Affiliate disclosure
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex