Skip to main content

Tabs

Overview

Use the Tabs family when you need a tabbed interface where one content panel is visible at a time, selected by clicking its corresponding trigger. The family is composed, not standalone — the root component manages shared state and keyboard navigation, while child components define the trigger list and content panels. Tabs work well for product feature sections, settings panels, pricing tiers, and any content that benefits from organizing information into mutually exclusive views.

Authoring Structure

Tabs
├── TabsList
│ ├── TabsTrigger (1st)
│ ├── TabsTrigger (2nd)
│ └── TabsTrigger (3rd)
├── TabsContent (1st)
├── TabsContent (2nd)
└── TabsContent (3rd)

Placement Rules

ComponentPlacementRole
TabsTop-level wrapper.Owns state, keyboard navigation, and shared options.
TabsListDirect child of Tabs.Container for trigger buttons. Receives role="tablist".
TabsTriggerInside TabsList.Interactive button that activates its paired content panel.
TabsContentDirect child of Tabs, sibling to TabsList.The content panel that shows or hides based on the active trigger.

Pairing Rule

Triggers and content panels are paired by position — the first TabsTrigger inside TabsList pairs with the first TabsContent that is a direct child of Tabs. The second pairs with the second, and so on. This is purely positional; labels and IDs are not used for matching. If trigger and content counts mismatch, extra triggers are disabled and extra content panels stay hidden.

Quick Start

Basic horizontal tabs
<OmeTabs settings='{{"orientation":"horizontal"}}'>
{#slot default}
<OmeTabsList>
{#slot default}
<OmeTabsTrigger content='{{"label":"Overview"}}' />
<OmeTabsTrigger content='{{"label":"Details"}}' />
<OmeTabsTrigger content='{{"label":"Changelog"}}' />
{/slot}
</OmeTabsList>
<OmeTabsContent>
{#slot default}<p>Overview content goes here.</p>{/slot}
</OmeTabsContent>
<OmeTabsContent>
{#slot default}<p>Details content goes here.</p>{/slot}
</OmeTabsContent>
<OmeTabsContent>
{#slot default}<p>Changelog content goes here.</p>{/slot}
</OmeTabsContent>
{/slot}
</OmeTabs>
Vertical tabs with responsive accordion
<OmeTabs
settings='{{"orientation":"vertical"}}'
responsiveAccordion='{{"enableResponsiveAccordion":true,"accordionBreakpoint":"768","showAccordionIndicator":true}}'
>
{#slot default}
<OmeTabsList styling='{{"class":"ome-tabs-list-default vertical-nav"}}'>
{#slot default}
<OmeTabsTrigger styling='{{"class":"ome-tabs-trigger-default nav-item"}}'>
{#slot default}Features{/slot}
</OmeTabsTrigger>
<OmeTabsTrigger styling='{{"class":"ome-tabs-trigger-default nav-item"}}'>
{#slot default}Pricing{/slot}
</OmeTabsTrigger>
<OmeTabsTrigger styling='{{"class":"ome-tabs-trigger-default nav-item"}}'>
{#slot default}FAQ{/slot}
</OmeTabsTrigger>
{/slot}
</OmeTabsList>
<OmeTabsContent>
{#slot default}<p>Features content.</p>{/slot}
</OmeTabsContent>
<OmeTabsContent>
{#slot default}<p>Pricing content.</p>{/slot}
</OmeTabsContent>
<OmeTabsContent>
{#slot default}<p>FAQ content.</p>{/slot}
</OmeTabsContent>
{/slot}
</OmeTabs>

Family Components

Tabs (Root)

The root component wraps all lists and panels, and manages shared behavior: active tab state, keyboard navigation, orientation, and optional responsive accordion mode. It does not render any visible UI itself — only a container element.

Structure Props

PropTypeDefaultDescription
tagstringdiv

HTML tag for the root element.

Settings Props

PropTypeDefaultDescription
orientation"horizontal" | "vertical"horizontal

Controls layout direction and keyboard behavior. "horizontal" uses ArrowLeft/ArrowRight for navigation. "vertical" uses ArrowUp/ArrowDown.

defaultIndexstring"0"

Zero-based index of the tab that should be active on initial load. If the index is invalid or points to a disabled trigger, it falls back to the first enabled trigger.

disabledbooleanfalse

Disables the entire tabs interface when true. Individual triggers can still override this with their own disabled prop.

Styling Props

PropTypeDefaultDescription
classclassome-tabs-root-default

CSS class applied to the root element.

Responsive Accordion Props

PropTypeDefaultDescription
enableResponsiveAccordionbooleanfalse

Enables responsive accordion mode. When the viewport width is below accordionBreakpoint, the runtime restructures the tabs into an accordion.

accordionBreakpointstring"768"

Viewport width in pixels below which accordion mode activates. Only applies when enableResponsiveAccordion is true.

etchPreviewMode"auto" | "tabs" | "accordion"auto

Controls Etch editor preview behavior. "auto" follows the canvas width. "tabs" always shows tabs. "accordion" always shows the accordion. Only applies when responsive accordion is enabled.

accordionAnimationDurationstring"300"

Animation duration in milliseconds for accordion panel expand/collapse. Only applies when responsive accordion is enabled.

showAccordionIndicatorbooleanfalse

Adds indicator spans to triggers in accordion mode. Only applies when responsive accordion is enabled.

accordionItemClassclass

CSS class applied to each generated accordion item wrapper. Only applies when responsive accordion is enabled.

accordionHeaderClassclass

CSS class applied to each generated accordion header wrapper. Only applies when responsive accordion is enabled.

accordionIndicatorClassclass

CSS class applied to generated accordion indicators. Only applies when showAccordionIndicator is true.

Keyboard Behavior — Tabs Mode

When a trigger has focus in tabs mode:

KeyAction
Enter / SpaceActivate the focused trigger.
ArrowRightMove focus to the next trigger and activate it (horizontal). Wraps to first. RTL-aware.
ArrowLeftMove focus to the previous trigger and activate it (horizontal). Wraps to last. RTL-aware.
ArrowDownMove focus to the next trigger and activate it (vertical). Wraps to first.
ArrowUpMove focus to the previous trigger and activate it (vertical). Wraps to last.
HomeFocus and activate the first enabled trigger.
EndFocus and activate the last enabled trigger.

In horizontal tabs, arrow keys automatically reverse in RTL layouts.

Keyboard Behavior — Accordion Mode

When the responsive accordion is active, keyboard behavior changes to match the Accordion component:

KeyAction
Enter / SpaceToggle the associated panel.
ArrowDownMove focus to the next trigger (does not activate).
ArrowUpMove focus to the previous trigger (does not activate).
HomeMove focus to the first trigger (does not activate).
EndMove focus to the last trigger (does not activate).

Focus movement in accordion mode does not auto-activate — you must click or press Enter/Space to open a panel.

Responsive Accordion

When enabled and the viewport drops below accordionBreakpoint, the runtime:

  1. Creates an accordion list element.
  2. Moves each trigger-content pair into accordion item wrappers.
  3. Replaces original positions with comment placeholders.
  4. Changes ARIA roles (tabbutton, tabpanelregion).
  5. Adds indicators if showAccordionIndicator is true.

When the viewport exceeds the breakpoint, everything is restored to the original tabs DOM.

Accessibility

The root component generates unique IDs for each trigger-content pair and wires up ARIA attributes automatically:

  • TabsList: role="tablist", aria-orientation synced to the orientation setting by the runtime.
  • TabsTrigger: role="tab", aria-selected, aria-controls pointing to its content panel, tabIndex (0 when active, -1 when inactive).
  • TabsContent: role="tabpanel", aria-labelledby pointing to its trigger, tabIndex="0".
  • Disabled triggers: aria-disabled="true", tabIndex="-1".
  • In accordion mode, triggers use role="button" and aria-expanded; content panels use role="region".

TabsList

The TabsList is the container for all TabsTrigger elements. It receives role="tablist" and manages focus roving for its child triggers. Place it as a direct child of the root Tabs component.

Styling Props

PropTypeDefaultDescription
classclassome-tabs-list-default

CSS class applied to the list element.


TabsTrigger

The interactive button that activates its paired content panel. When the slot is empty, the trigger renders its content.label value as text. Triggers are paired with content panels by position — the first trigger in TabsList activates the first TabsContent child of Tabs.

Settings Props

PropTypeDefaultDescription
disabledbooleanfalse

Disables this trigger independently of the root disabled setting. A disabled trigger cannot receive focus or become active, and receives aria-disabled="true".

Content Props

PropTypeDefaultDescription
labelstring""

Fallback label text. Used when the trigger slot is empty.

Styling Props

PropTypeDefaultDescription
classclassome-tabs-trigger-default

CSS class applied to the trigger element.

Trigger Slot Content

When the trigger slot is populated, the slot content replaces the fallback label. Use this for rich trigger content — icons, badges, or any markup beyond plain text:

<TabsTrigger>
{#slot default}
<span class="icon">⚙</span> Settings
{/slot}
</TabsTrigger>

TabsContent

The content panel that shows or hides based on the active trigger. Only one panel is visible at a time — Tabs always keeps exactly one panel active. Place content panels as direct children of the root Tabs component, as siblings to TabsList.

Styling Props

PropTypeDefaultDescription
classclassome-tabs-content-default

CSS class applied to the content panel.


CSS Custom Properties

The default tab styles reference these CSS custom properties. Override them to customize appearance without replacing the default classes:

VariableUsed ForFallback
--space-sGap between list and content panels.1rem
--btn-padding-blockTrigger padding (block axis).
--btn-padding-inlineTrigger padding (inline axis).
--border-color-darkInactive trigger border color.
--text-dark-mutedInactive trigger text color.
--primaryActive trigger border color.
--heading-colorActive trigger text color.
--ome-tabs-accordion-animation-durationAccordion animation duration (set inline from the accordionAnimationDuration prop).

Pattern Examples

Vertical Tabbed Timeline

Used by the Testimonial8 pattern — a vertical tabbed timeline with custom styled trigger cards:

  • orientation="vertical"
  • 4 triggers (Discovery, Onboarding, Building, Scaling) inside custom styled cards
  • 4 content panels with testimonials
  • Two-column grid layout (0.35fr / 0.65fr)

Vertical Tabbed Features with Images

Used by the FeatureSection20 pattern — vertical tabs with image content panels:

  • orientation="vertical"
  • 4 triggers as feature cards with icons
  • 4 content panels showing images
  • Two-column grid layout (0.45fr / 0.55fr)

Both patterns use vertical orientation and custom styling classes to override the default trigger appearance.


Common Mistakes

Nesting TabsContent inside TabsList

TabsContent panels must be direct children of the root Tabs component, not inside TabsList. The runtime pairs triggers and content by scanning TabsList children for triggers and Tabs children (excluding TabsList) for content panels. Placing content inside TabsList breaks pairing.

Mismatched trigger and content counts

If you have more triggers than content panels, the extra triggers are disabled. If you have more content panels than triggers, the extra panels stay hidden. Always ensure the counts match.

Expecting focus to activate tabs in accordion mode

In tabs mode, arrow keys move focus and activate the tab. In accordion mode (when responsive accordion is active), arrow keys only move focus — you must click or press Enter/Space to open a panel.

Expecting pairing by label or ID

Triggers and content panels are paired by position only. The second TabsTrigger always pairs with the second TabsContent, regardless of labels or any other attribute. Reordering one without the other breaks the pairing.


FAQs

Can I have nested tabs?

Yes. You can place a Tabs component inside a TabsContent panel. Each nested tabs instance manages its own state independently. Be mindful of the visual complexity — deeply nested tabs can be difficult to navigate.

How does the responsive accordion work?

When enableResponsiveAccordion is true and the viewport drops below accordionBreakpoint, the runtime restructures the DOM into accordion items. Triggers become accordion headers with role="button" and aria-expanded, and content panels become regions. When the viewport exceeds the breakpoint, the original tabs DOM is restored. The transition is seamless — no page reload required.

What if I need all panels collapsed?

Tabs always keeps exactly one panel active. If you need the ability to collapse all panels, use the Accordion component with type="single" instead.

Can I change the default active tab?

Yes. Use the defaultIndex setting on the root Tabs component. It is zero-based — "0" activates the first tab, "1" the second, and so on. If the index is invalid or points to a disabled trigger, it falls back to the first enabled trigger.

How do vertical tabs work with keyboard navigation?

In vertical orientation, ArrowUp and ArrowDown navigate between triggers instead of ArrowLeft and ArrowRight. Home and End still jump to the first and last enabled triggers. The arrow keys auto-activate the focused tab, same as horizontal mode.