Components

Tabs

A set of layered panels where only one is visible at a time.

Accessible, unstyled components for modern web applications.

Usage

<div id="mcr:tabs:demo" data-orientation="horizontal">
  <div role="tablist" aria-orientation="horizontal">
    <button
      role="tab"
      id="mct:tabs:t1"
      aria-selected="true"
      aria-controls="mcc:tabs:t1"
      tabindex="0"
    >
      Overview
    </button>
    <button
      role="tab"
      id="mct:tabs:t2"
      aria-selected="false"
      aria-controls="mcc:tabs:t2"
      tabindex="-1"
    >
      Features
    </button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:t1"
    aria-labelledby="mct:tabs:t1"
    aria-hidden="false"
    tabindex="0"
  >
    Overview content...
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:t2"
    aria-labelledby="mct:tabs:t2"
    aria-hidden="true"
    hidden="until-found"
    tabindex="-1"
  >
    Features content...
  </div>
</div>

Examples

Default Tab

Specify which tab is selected initially.

This tab is selected by default because defaultValue="second".
<div id="mcr:tabs:default" data-orientation="horizontal">
  <div role="tablist" aria-orientation="horizontal">
    <button
      role="tab"
      id="mct:tabs:d1"
      aria-selected="false"
      aria-controls="mcc:tabs:d1"
      tabindex="-1"
    >
      First
    </button>
    <button
      role="tab"
      id="mct:tabs:d2"
      aria-selected="true"
      aria-controls="mcc:tabs:d2"
      tabindex="0"
    >
      Second (Default)
    </button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:d1"
    aria-labelledby="mct:tabs:d1"
    aria-hidden="true"
    hidden="until-found"
    tabindex="-1"
  >
    First tab content
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:d2"
    aria-labelledby="mct:tabs:d2"
    aria-hidden="false"
    tabindex="0"
  >
    This tab is selected by default
  </div>
</div>

Vertical Orientation

Use vertical layout for side navigation.

Manage your account settings and preferences.
<div id="mcr:tabs:vertical" data-orientation="vertical">
  <div role="tablist" aria-orientation="vertical">
    <button
      role="tab"
      id="mct:tabs:v1"
      aria-selected="true"
      aria-controls="mcc:tabs:v1"
      tabindex="0"
    >
      Account
    </button>
    <button
      role="tab"
      id="mct:tabs:v2"
      aria-selected="false"
      aria-controls="mcc:tabs:v2"
      tabindex="-1"
    >
      Security
    </button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:v1"
    aria-labelledby="mct:tabs:v1"
    aria-hidden="false"
    tabindex="0"
  >
    Account settings...
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:v2"
    aria-labelledby="mct:tabs:v2"
    aria-hidden="true"
    hidden="until-found"
    tabindex="-1"
  >
    Security options...
  </div>
</div>

Disabled Tabs

Disabled tabs cannot be selected and are skipped during keyboard navigation.

Update your name, email, and profile picture.
<div id="mcr:tabs:settings" data-orientation="horizontal">
  <div role="tablist" aria-orientation="horizontal">
    <button
      role="tab"
      id="mct:tabs:s1"
      aria-selected="true"
      aria-controls="mcc:tabs:s1"
      tabindex="0"
    >
      Profile
    </button>
    <button
      role="tab"
      id="mct:tabs:s2"
      aria-selected="false"
      aria-controls="mcc:tabs:s2"
      tabindex="-1"
      aria-disabled="true"
    >
      Billing (Locked)
    </button>
    <button
      role="tab"
      id="mct:tabs:s3"
      aria-selected="false"
      aria-controls="mcc:tabs:s3"
      tabindex="-1"
    >
      Notifications
    </button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:s1"
    aria-labelledby="mct:tabs:s1"
    aria-hidden="false"
    tabindex="0"
  >
    Update your profile...
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:s2"
    aria-labelledby="mct:tabs:s2"
    aria-hidden="true"
    hidden="until-found"
    tabindex="-1"
  >
    Billing settings...
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:s3"
    aria-labelledby="mct:tabs:s3"
    aria-hidden="true"
    hidden="until-found"
    tabindex="-1"
  >
    Notification preferences...
  </div>
</div>

Non-Focusable Panels

By default, panels are focusable so keyboard users always have somewhere to land after selecting a tab. When panels contain meaningful focusable elements like buttons or links, this can be turned off by setting focusable to false.

Press Tab from the tab list. The panel receives focus first, then the button.
<div id="mcr:tabs:focusable" data-orientation="horizontal">
  <div role="tablist" aria-orientation="horizontal">
    <button
      role="tab"
      id="mct:tabs:f1"
      aria-selected="true"
      aria-controls="mcc:tabs:f1"
      tabindex="0"
    >
      Focusable (default)
    </button>
    <button
      role="tab"
      id="mct:tabs:f2"
      aria-selected="false"
      aria-controls="mcc:tabs:f2"
      tabindex="-1"
    >
      Non-Focusable
    </button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:f1"
    aria-labelledby="mct:tabs:f1"
    aria-hidden="false"
    tabindex="0"
  >
    <button>Save</button>
    <button>Delete</button>
  </div>
  <div
    role="tabpanel"
    id="mcc:tabs:f2"
    aria-labelledby="mct:tabs:f2"
    aria-hidden="true"
    hidden="until-found"
  >
    <button>Save</button>
    <button>Delete</button>
  </div>
</div>

Accessibility

Follows the WAI-ARIA Tabs Pattern.

Keyboard

Horizontal

KeyAction
Arrow RightMove focus to the next tab
Arrow LeftMove focus to the previous tab
HomeMove focus to the first tab
EndMove focus to the last tab

Vertical

KeyAction
Arrow DownMove focus to the next tab
Arrow UpMove focus to the previous tab
HomeMove focus to the first tab
EndMove focus to the last tab

Disabled tabs are skipped during keyboard navigation. Hidden panels use hidden="until-found" so content remains searchable via browser find-in-page (Ctrl+F).

API Reference

JavaScript

ElementAttributesDescription
Containerid="mcr:tabs:*"Root ID (required)
data-orientation"horizontal" or "vertical"
Tablistrole="tablist", aria-orientationContainer for tab buttons
Tabrole="tab"Tab button
id="mct:tabs:*"Tab ID (required)
aria-selected"true" or "false"
aria-controlsPanel element ID
tabindex0 for selected, -1 for others
aria-disabled="true"For disabled tabs
Panelrole="tabpanel"Tab content panel
id="mcc:tabs:*"Panel ID (required)
aria-labelledbyTab trigger ID
aria-hidden"true" or "false"
hidden="until-found"Set on hidden panels, removed when visible
tabindex0 for visible, -1 for hidden. Omit if the panel contains focusable content.

Frameworks

All components accept className (React) or standard attributes (Vue) for styling. The Vue API is identical - use kebab-case in templates for multi-word props (e.g., default-value instead of defaultValue).

Tabs.Root

PropTypeDefaultDescription
defaultValuestringRequiredInitially selected tab value
orientation"horizontal" | "vertical""horizontal"Layout direction

Tabs.List

Container for the tab triggers.

Tabs.Tab

PropTypeDefaultDescription
valuestringRequiredLinks this trigger to a panel
disabledbooleanfalseWhether the tab is disabled (cannot be selected)

Tabs.Panel

PropTypeDefaultDescription
valuestringRequiredLinks this panel to a trigger
focusablebooleantrueSet to false if the panel contains focusable content

Frequently Asked Questions