import { Tabs } from "@elabs-ai/components-ui";npm install @elabs-ai/components-tokens @elabs-ai/components-uiPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui Tabs component (from @elabs-ai/components-ui) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look Tabs up before writing code: `npx -y @elabs-ai/components-cli docs Tabs` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-ui is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Avoid
- Tabs for sequential steps — use a stepper/wizard; tabs are for peer, non-sequential views.
- More than ~7 tabs — consider a Select or a sidebar nav.
Works with
Contains
Examples
Overflow Scrollable
An 8-tab strip inside a 320px-wide container (phone-width) — narrower than the strip's natural width, so it must scroll rather than clip. Confirms every tab is reachable by keyboard: repeated `ArrowRight` walks focus/ selection all the way to the last tab, and the newly-active tab scrolls itself into the container's visible bounds (real layout — this assertion only means anything in a real browser, which is how `test-storybook` / `run-story-tests` execute it).
Wide Viewport Fits
Regression guard: a strip that FITS its container renders exactly as before — `justify-center-safe` behaves identically to `justify-center` when there's nothing to overflow, so no scrollbar appears — AND mounting it moves NOTHING. The tabs sit below a tall spacer inside a scroll region, out of view. The first cut of #344 called `Element.scrollIntoView()` on mount, which walks every scrollable ancestor: it scrolled this region (and the page) to reveal the tabs nobody had asked for. Asserting "no scrollbar" alone did not catch that — asserting the scroll POSITION does.
Programmatic Activation
The scroll-on-activation behaviour proved WITHOUT the browser's own focus-scrolling doing the work: a button beside the strip sets the value programmatically, so the newly-active tab is never focused and nothing but `TabsTrigger`'s own effect can bring it into view. The strip starts at scroll 0 (mount is not an activation), then moves — and only the strip moves.
Underline
`variant="underline"` — line tabs: a transparent row over a 1px rule, the active tab marked by a 2px `--primary` underline and foreground ink. Label weight follows the theme's `--control-weight`. Keyboard behaviour is Radix's, identical to the segmented default.
Rail
`variant="rail"` — vertical tabs for `<Tabs orientation="vertical">`: the list stacks, the active tab carries a start-edge `--primary` bar plus a muted fill, and a rich trigger (icon, title, one-line summary) wraps instead of truncating. Radix drives arrow-up/down between the tabs.
Token Driven Underline
Token-driven default (#392): NO `variant` prop on `TabsList` — an ancestor setting `--tabs-variant: underline` renders the exact same look as the explicit `Underline` story above, entirely via CSS (a theme could set this token instead of a region wrapper). Neither the list nor its triggers carry a `data-variant` attribute here.
API
TabsContent
TabsList
TabsTrigger
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| list | TabsList: bg-muted text-muted-foreground |
| active | data-[state=active]: bg-surface-elevated text-foreground shadow-sm |
| focus | focus-ring — the compound indicator: a ring-2 in --ring plus a 1px --ring-contour outline (#67) + ring-offset-2 ring-offset-background |