You're viewing the ds deployment.

Tabs

Alternative contents, one shown at a time

Summary

Alternative contents, one shown at a time.

Usage

<Tabs defaultValue="react">
  <TabsList>
    <TabsTrigger value="react">React</TabsTrigger>
    <TabsTrigger value="vue">Vue</TabsTrigger>
    <TabsTrigger value="svelte">Svelte</TabsTrigger>
  </TabsList>
  <TabsContent value="react">

    | Aspect | React |
    | --- | --- |
    | Type | Library |
    | Templates | JSX |
    | Reactivity | Re-render on state change |

  </TabsContent>
  <TabsContent value="vue">

    | Aspect | Vue |
    | --- | --- |
    | Type | Framework |
    | Templates | Single-file components with HTML templates |
    | Reactivity | Reactive proxies |

  </TabsContent>
  <TabsContent value="svelte">

    | Aspect | Svelte |
    | --- | --- |
    | Type | Compiler |
    | Templates | `.svelte` files |
    | Reactivity | Compiled at build time |

  </TabsContent>
</Tabs>

result:

AspectReact
TypeLibrary
TemplatesJSX
ReactivityRe-render on state change

Same API as shadcn Tabs.

defaultValue is required (otherwise no tab is shown until hydration).

Markdown inside a TabsContent (a table, a code block, a list) needs a blank line after the opening tag and before the closing one.

Rich triggers

Triggers accept any markup:

<Tabs defaultValue="react">
  <TabsList>
    <TabsTrigger value="react">React <Badge>19</Badge></TabsTrigger>
    <TabsTrigger value="vue">Vue</TabsTrigger>
  </TabsList>
  <TabsContent value="react">

    | Aspect | React |
    | --- | --- |
    | Version | 19 |
    | Released | December 2024 |

  </TabsContent>
  <TabsContent value="vue">

    | Aspect | Vue |
    | --- | --- |
    | Version | 3 |
    | Released | September 2020 |

  </TabsContent>
</Tabs>

result:

AspectReact
Version19
ReleasedDecember 2024

Synced tabs

Tabs of the same syncKey switch together: picking a tab in one picks it in every other, on this page and on the others. The pick is remembered (in localStorage), after a reload and in other browser tabs.

<Tabs defaultValue="react" syncKey="framework">
  <TabsList>
    <TabsTrigger value="react">React</TabsTrigger>
    <TabsTrigger value="vue">Vue</TabsTrigger>
    <TabsTrigger value="svelte">Svelte</TabsTrigger>
  </TabsList>
  <TabsContent value="react">

    Templates are JSX: markup in JavaScript.

  </TabsContent>
  <TabsContent value="vue">

    Templates are HTML, in single-file components.

  </TabsContent>
  <TabsContent value="svelte">

    Templates are HTML, in `.svelte` files.

  </TabsContent>
</Tabs>

<Tabs defaultValue="react" syncKey="framework">
  <TabsList>
    <TabsTrigger value="react">React</TabsTrigger>
    <TabsTrigger value="svelte">Svelte</TabsTrigger>
  </TabsList>
  <TabsContent value="react">

    State changes re-render the component.

  </TabsContent>
  <TabsContent value="svelte">

    State changes are compiled into DOM updates.

  </TabsContent>
</Tabs>

result:

Templates are JSX: markup in JavaScript.

State changes re-render the component.

A Tabs without the picked tab stays as it is: pick Svelte above, then Vue, and the second one stays on Svelte. The pick is still Vue, for the next Tabs that has it. A page loaded with a pick none of its tabs have shows their defaultValue.

The page's HTML shows defaultValue: the picked tab replaces it once the page's JavaScript runs. A link to a #hash in a hidden panel opens its tab as a click would, so it picks that tab too.