Skip to main content
The Tabs component provides a tabbed navigation interface for switching between different content panels.

Basic Usage

import reflex_ui as ui

ui.tabs.root(
    ui.tabs.list(
        ui.tabs.tab("Tab 1", value="1"),
        ui.tabs.tab("Tab 2", value="2"),
        ui.tabs.tab("Tab 3", value="3"),
        ui.tabs.indicator()
    ),
    ui.tabs.panel("Content for tab 1", value="1"),
    ui.tabs.panel("Content for tab 2", value="2"),
    ui.tabs.panel("Content for tab 3", value="3"),
    default_value="1"
)

Controlled State

class State(rx.State):
    active_tab: str = "home"

ui.tabs.root(
    ui.tabs.list(
        ui.tabs.tab("Home", value="home"),
        ui.tabs.tab("Profile", value="profile"),
        ui.tabs.tab("Settings", value="settings"),
        ui.tabs.indicator()
    ),
    ui.tabs.panel("Home content", value="home"),
    ui.tabs.panel("Profile content", value="profile"),
    ui.tabs.panel("Settings content", value="settings"),
    value=State.active_tab,
    on_value_change=State.set_active_tab
)

Props Reference

tabs.root

value
str | int
Currently selected tab (controlled)
default_value
str | int
default:"0"
Initially selected tab (uncontrolled)
on_value_change
EventHandler[str]
Event fired when tab selection changes
orientation
Literal['horizontal', 'vertical']
default:"horizontal"
Layout direction of tabs

tabs.list

activate_on_focus
bool
default:"False"
Whether arrow keys automatically activate tabs (vs requiring Enter/Space)
loop_focus
bool
default:"True"
Whether arrow keys loop to first tab at the end

tabs.tab

value
str | int
required
Unique identifier for this tab
disabled
bool
default:"False"
Disables this tab

tabs.panel

value
str | int
required
Matches the corresponding tab’s value
keep_mounted
bool
default:"False"
Keep panel in DOM when not active (for animations)

With Icons

ui.tabs.root(
    ui.tabs.list(
        ui.tabs.tab(
            ui.icon("HomeIcon"),
            "Home",
            value="home"
        ),
        ui.tabs.tab(
            ui.icon("UserIcon"),
            "Profile",
            value="profile"
        ),
        ui.tabs.indicator()
    ),
    ui.tabs.panel("Home content", value="home"),
    ui.tabs.panel("Profile content", value="profile"),
    default_value="home"
)

Vertical Orientation

ui.tabs.root(
    ui.tabs.list(
        ui.tabs.tab("Section 1", value="1"),
        ui.tabs.tab("Section 2", value="2"),
        ui.tabs.indicator()
    ),
    ui.tabs.panel("Content 1", value="1"),
    ui.tabs.panel("Content 2", value="2"),
    orientation="vertical",
    class_name="flex-row gap-4"
)

Disabled Tabs

ui.tabs.list(
    ui.tabs.tab("Active", value="1"),
    ui.tabs.tab("Disabled", value="2", disabled=True),
    ui.tabs.tab("Active", value="3"),
    ui.tabs.indicator()
)

Dynamic Tabs

class State(rx.State):
    tabs: list[dict] = [
        {"value": "tab1", "label": "Tab 1", "content": "Content 1"},
        {"value": "tab2", "label": "Tab 2", "content": "Content 2"}
    ]

ui.tabs.root(
    ui.tabs.list(
        rx.foreach(
            State.tabs,
            lambda tab: ui.tabs.tab(tab["label"], value=tab["value"])
        ),
        ui.tabs.indicator()
    ),
    rx.foreach(
        State.tabs,
        lambda tab: ui.tabs.panel(tab["content"], value=tab["value"])
    )
)

Tabs Components

tabs.root

Container for the entire tabs component.
ui.tabs.root(
    ui.tabs.list(...),
    ui.tabs.panel(...),
    default_value="1"
)

tabs.list

Container for tab buttons.
ui.tabs.list(
    ui.tabs.tab("Tab 1"),
    ui.tabs.tab("Tab 2"),
    ui.tabs.indicator(),
    activate_on_focus=True
)

tabs.tab

Individual tab button.
ui.tabs.tab(
    "Label",
    value="unique-id",
    disabled=False
)

tabs.indicator

Animated visual indicator showing active tab.
ui.tabs.indicator()  # Animates position and width
render_before_hydration
bool
default:"False"
Render indicator before React hydrates (reduces flash)

tabs.panel

Content area for a tab.
ui.tabs.panel(
    rx.text("Panel content"),
    value="matching-tab-id",
    keep_mounted=True
)

Styling Classes

Access predefined styles via ui.tabs.class_names:
  • ROOT: Flex column layout
  • LIST: Rounded background with padding, inline flex
  • TAB: Tab button with hover and selected states
  • INDICATOR: Animated underline/background indicator
  • PANEL: Content panel layout

Indicator Animation

The indicator automatically:
  • Moves to the active tab’s position
  • Resizes to match the active tab’s width
  • Uses smooth CSS transitions (200ms ease-in-out)
  • Positioned using CSS custom properties

Keyboard Navigation

  • Arrow Left/Up: Move to previous tab
  • Arrow Right/Down: Move to next tab
  • Home: Jump to first tab
  • End: Jump to last tab
  • Enter/Space: Activate focused tab (when activate_on_focus=False)

Accessibility

  • Proper ARIA roles and attributes
  • Keyboard navigation support
  • Focus management
  • Screen reader announcements
  • Selected state indication

Common Patterns

With State Management

class AppState(rx.State):
    tab: str = "overview"
    
    def handle_tab_change(self, value: str):
        self.tab = value
        # Perform side effects like fetching data
        if value == "analytics":
            self.load_analytics()

Conditional Content

ui.tabs.panel(
    rx.cond(
        State.data_loaded,
        display_data(),
        ui.spinner()
    ),
    value="data-tab"
)

Implementation Details

From source code at reflex_ui/components/base/tabs.py:36:
  • Built on Base UI Tabs primitives
  • Indicator uses CSS custom properties for position/size
  • Tab button has hover and selected states
  • List has background and rounded corners
  • Panel content can be kept mounted for animations

Build docs developers (and LLMs) love