# Tabs
URL: https://remocn.dev/docs/ui/components/tabs

A tabs widget whose active-tab state is a pure function of the timeline

- Install: `npx shadcn@latest add @remocn/tabs`
- Vibe: clean
- Natural length: 120f @ 30fps

A state atom whose appearance (which tab is active) is a pure function of the `state` prop. Pass a state directly or drive it from the Remotion timeline via `useCurrentState`. The component reads no frame — only the state value you give it.

## Installation

```bash
npx shadcn@latest add @remocn/tabs
```

> **Note**
>
> Installing `tabs` automatically installs the shared `remocn-ui` core (`lib/remocn-ui/`) via `registryDependencies`. You do not need to install it separately.

## States

`TabsState` is:

```ts
type TabsState = string;
```

The `state` is the active tab's value — one of the strings in the `items` array. When the state changes, the active label brightens, the indicator (pill or underline) slides to that tab, and the matching panel crossfades in.

## Snap usage

Pass `state` directly — the component snaps instantly to that visual. Useful for static previews or when you drive state from your own logic:

```tsx
import { Tabs } from "@/components/remocn/tabs";

export const Scene = () => <Tabs state="Password" />;
```

To drive state from the timeline, use `useCurrentState`:

```tsx
import { useCurrentState } from "@/lib/remocn-ui";
import { Tabs } from "@/components/remocn/tabs";

export const Scene = () => {
  const state = useCurrentState(
    [
      { at: 18, state: "Account" },
      { at: 58, state: "Password" },
      { at: 94, state: "Settings" },
    ],
    "Account",
  );

  return <Tabs state={state} />;
};
```

Each `at` is a **Sequence-local authored frame**. State persists between steps: `Account` at frame 18 keeps that tab active until `Password` fires at frame 58. See [Concepts](/docs/ui/concepts) for the full `useCurrentState` API.

## Smooth transitions

State changes via `state` snap with no cross-fade. For animated transitions, use `useTabsTransition` from the copied `use-tabs-transition.ts` file. It reads the frame, interpolates between state presets, and returns a resolved `TabsStyle` — pass it to the `style` prop:

```tsx
import { Tabs } from "@/components/remocn/tabs";
import { useTabsTransition } from "@/components/remocn/use-tabs-transition";

export const Scene = () => {
  const style = useTabsTransition([
    { at: 18, state: "Account", duration: 16 },
    { at: 58, state: "Password", duration: 18 },
    { at: 94, state: "Settings", duration: 12 },
  ]);
  return <Tabs style={style} />;
};
```

The `duration` field on each step overrides the file's `DEFAULT_DURATION` for that specific transition. To globally tune timing and easing, edit `use-tabs-transition.ts` directly in your project — that file is yours (shadcn "own your code" philosophy).

`style` takes precedence over `state` when both are provided.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `state` | `string` | `"Account"` | Current visual state (snap path). One of the values in items. State changes snap — no automatic cross-fade. |
| `style` | `TabsStyle` | — | Resolved animated visual (smooth path). Pass an interpolated TabsStyle from useTabsTransition. Takes precedence over state when provided. |
| `items` | `string[]` | `["Account", "Password", "Settings"]` | Tab labels (and values). The active state is one of these strings. |
| `contents` | `string[]` | `Parallel strings for each tab` | Panel body text per tab — parallel to items. Displayed with opacity crossfade. |
| `contentHeight` | `number` | `72` | Authored panel height in px. Required because Remotion cannot measure auto height — set this to match the rendered height of your content. |
| `variant` | `"pill" \| "underline"` | `"pill"` | Visual variant. pill wraps the indicator in a muted track; underline runs a bar along the bottom. |
| `theme` | `Partial<RemocnTheme>` | — | Per-component theme override. Merges with the active RemocnUIProvider theme and the mode defaults. Must be concrete oklch/hex/rgb values — not CSS custom properties. |
| `mode` | `"light" \| "dark"` | `"light"` | Sets the base palette. Overrides the mode on RemocnUIProvider when both are present. |
| `className` | `string` | — | Optional className passed to the tabs element. |

## Use when
- Showing a tabbed UI switching between content panels in a product demo.
- Demonstrating a pill or underline tab indicator sliding between sections.
- Showing a content crossfade between different sections of a layout.

## Don't use when
- Steps are sequential and ordered — use `stepper` instead (numbered connectors + check marks).
- You need a compact segmented control without content panels — use `toggle-group` instead (no panel area).
- You need a sidebar or vertical navigation structure — compose a nav layout directly instead.