# Tabs

A set of layered sections that show one panel at a time.

## Installation

```tsx
import { Tabs, TabsList, Tab, TabsPanel } from "@makeplane/propel/components/tabs";
```
## Usage

```tsx
import { Icon } from "@makeplane/propel/components/icon";
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";
import { ActivityOutline, GridLayoutOutline, SettingsOutline } from "@makeplane/propel/icons";
import * as React from "react";

const TAB_ITEMS = [
  {
    value: "overview",
    label: "Overview",
    icon: <Icon icon={GridLayoutOutline} />,
    panel: "A high-level summary of the project.",
  },
  {
    value: "activity",
    label: "Activity",
    icon: <Icon icon={ActivityOutline} />,
    panel: "The latest activity feed.",
  },
  {
    value: "settings",
    label: "Settings",
    icon: <Icon icon={SettingsOutline} />,
    panel: "Configuration and preferences.",
  },
];

export default function BasicDemo() {
  const [value, setValue] = React.useState("overview");

  return (
    <div className="w-96">
      <Tabs variant="contained" value={value} onValueChange={setValue}>
        <TabsList>
          {TAB_ITEMS.map((tab) => (
            <Tab key={tab.value} value={tab.value} icon={tab.icon} label={tab.label} />
          ))}
        </TabsList>
        {TAB_ITEMS.map((tab) => (
          <TabsPanel key={tab.value} value={tab.value}>
            {tab.panel}
          </TabsPanel>
        ))}
      </Tabs>
    </div>
  );
}
```
## Examples

### Underline

The `underline` variant keeps the tabs flat and slides a bar under the active one, in contrast to the raised `contained` pill.

```tsx
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";

const TAB_ITEMS = [
  { value: "overview", label: "Overview", panel: "A high-level summary of the project." },
  { value: "activity", label: "Activity", panel: "The latest activity feed." },
  { value: "settings", label: "Settings", panel: "Configuration and preferences." },
];

export default function UnderlineDemo() {
  return (
    <div className="w-96">
      <Tabs variant="underline" defaultValue="overview">
        <TabsList>
          {TAB_ITEMS.map((tab) => (
            <Tab key={tab.value} value={tab.value} label={tab.label} />
          ))}
        </TabsList>
        {TAB_ITEMS.map((tab) => (
          <TabsPanel key={tab.value} value={tab.value}>
            {tab.panel}
          </TabsPanel>
        ))}
      </Tabs>
    </div>
  );
}
```

### Full width

`stretch="full"` makes the tab row fill its container, splitting it into equal-width tabs — most visible on the `contained` segmented-control look.

```tsx
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";

export default function FullWidthDemo() {
  return (
    <div className="w-96">
      <Tabs variant="contained" stretch="full" defaultValue="overview">
        <TabsList>
          <Tab value="overview" label="Overview" />
          <Tab value="activity" label="Activity" />
          <Tab value="settings" label="Settings" />
        </TabsList>
        <TabsPanel value="overview">A high-level summary of the project.</TabsPanel>
        <TabsPanel value="activity">The latest activity feed.</TabsPanel>
        <TabsPanel value="settings">Configuration and preferences.</TabsPanel>
      </Tabs>
    </div>
  );
}
```

### Fill a bounded layout

`layout="fill"` makes the Tabs root and active panel consume a bounded parent's available height. Keep the parent in a bounded flex layout and put scrolling on the panel content. The default `layout="content"` preserves content-sized tabs.

```tsx
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";

export default function FillLayoutDemo() {
  return (
    <div className="flex h-48 min-h-0 w-96">
      <Tabs variant="underline" layout="fill" defaultValue="overview">
        <TabsList>
          <Tab value="overview" label="Overview" />
          <Tab value="activity" label="Activity" />
        </TabsList>
        <TabsPanel value="overview">
          <div className="h-full overflow-y-auto">A high-level summary of the project.</div>
        </TabsPanel>
        <TabsPanel value="activity">
          <div className="h-full overflow-y-auto">The latest activity feed.</div>
        </TabsPanel>
      </Tabs>
    </div>
  );
}
```

### Disabled

```tsx
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";

const TAB_ITEMS = [
  {
    value: "overview",
    label: "Overview",
    panel: "A high-level summary of the project.",
    disabled: false,
  },
  {
    value: "activity",
    label: "Activity",
    panel: "The latest activity feed.",
    disabled: false,
  },
  {
    value: "billing",
    label: "Billing",
    panel: "Upgrade the workspace to manage billing.",
    disabled: true,
  },
];

export default function DisabledDemo() {
  return (
    <div className="w-96">
      <Tabs variant="contained" defaultValue="overview">
        <TabsList>
          {TAB_ITEMS.map((tab) => (
            <Tab key={tab.value} value={tab.value} label={tab.label} disabled={tab.disabled} />
          ))}
        </TabsList>
        {TAB_ITEMS.map((tab) => (
          <TabsPanel key={tab.value} value={tab.value}>
            {tab.panel}
          </TabsPanel>
        ))}
      </Tabs>
    </div>
  );
}
```

### Overflow

`TabsList` composes a horizontal `ScrollArea`: when the tabs are wider than their container the row scrolls instead of overflowing, its overlay scrollbar fading in on hover or scroll. Keyboard navigation still reaches every tab — the focused tab is kept in view.

### Navigation tab

`Tab` composes a custom element or function `render` target through its styled element, preserving Propel styling and Base UI behavior. When it renders as a navigation link, set `nativeButton={false}` so Base UI does not apply native-button-only attributes to the anchor. `Tabs` and `TabsPanel` keep their standard rendered elements.

```tsx
import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs";

export default function CustomTabRenderTargetDemo() {
  return (
    <div className="w-96">
      <Tabs variant="underline" defaultValue="overview">
        <TabsList>
          <Tab
            value="overview"
            label="Overview"
            nativeButton={false}
            render={<a href="#overview" />}
          />
          <Tab value="activity" label="Activity" />
        </TabsList>
        <TabsPanel value="overview">A high-level summary of the project.</TabsPanel>
        <TabsPanel value="activity">The latest activity feed.</TabsPanel>
      </Tabs>
    </div>
  );
}
```
## API Reference

### Tabs

The ready-made tab set: grafts the Base UI `Tabs.Root` behavior onto the styled `elements/tabs`
root and shares its `variant`/`stretch` with the `TabsList`/`Tab`s and its `layout` with the
`TabsPanel`s inside via context, so you don't repeat them.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` (required) | `"contained" \| "underline"` | — | Whether the set is a contained pill or a flat underline strip. Shared with its parts. |
| `stretch` | `"auto" \| "full"` | `auto` | Whether the tab row shrinks to fit its tabs or fills its container, splitting it into equal-width tabs. Shared with its parts. Most visible on the `contained` look. |
| `layout` | `"content" \| "fill"` | `content` | Whether the set takes its content height or fills a bounded parent with flexible panels. |

### TabsList

The ready-made tab strip: grafts the Base UI `ScrollArea.Root` onto the styled
`TabsListScrollArea` frame, then grafts the Base UI `Tabs.List` behavior onto the styled
`TabsList` (taking the set's `variant`/`stretch` from context) rendered as the scroll viewport,
plus a horizontal scrollbar so a long row of tabs scrolls, and renders the active-tab underline
`TabsIndicator` for the `underline` variant.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, TabsListState>` | — | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `activateOnFocus` | `boolean` | `false` | Whether moving focus to a tab with the arrow keys also activates it immediately (updates `aria-selected` and swaps the panel), vs. only moving focus until committed with Enter/Space. Pass `true` to activate a tab as soon as arrow-key focus reaches it — costs a render/panel-swap per keystroke, so reach for it only when switching tabs is cheap and instant visual feedback matters more than avoiding that cost. |

### Tab

The ready-made tab button: grafts the Base UI `Tabs.Tab` behavior onto the styled `elements/tabs`
tab (taking the set's `variant`/`stretch` from context, so you don't pass them) and lays out an
optional `icon` with the label. The `underline` variant additionally renders the sliding bar
track beneath the label. A consumer `render` target is composed through the styled tab,
preserving Propel's classes; render callbacks receive the original Base UI tab state.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `string` | — | Visible tab label. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, TabsTabState>` | — | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `icon` | `ReactNode` | — | Element rendered before the label (inline-start), e.g. `<Icon icon={Folder} />`. |

### TabsPanel

The ready-made tab panel: grafts the Base UI `Tabs.Panel` behavior (shown when the `Tab` of the
matching `value` is active) onto the styled `elements/tabs` panel and applies the set's height
layout from context.

No component-specific props — accepts the standard HTML attributes for the element it renders.
