# Tab field (goodtabs UI)

Forty drifting browser tabs that settle into a calm grid of ten as progress rises.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/tab-field.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/tab-field`

## Usage

```tsx
import { TabField } from "@/components/ui/tab-field"

const progress = React.useRef(0)
// gsap.to(progress, { current: 1, scrollTrigger: { scrub: true } })

<TabField progress={progress} className="h-dvh pt-40" />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| progress | `number | { current: number }` | `0` | 0 is the drifting field, 1 the finished grid. A ref is read every frame with no re-render. |
| keep | `number` | `10` | How many of the 40 tabs stay. Half as many columns as tabs, or two columns in a narrow box. |
| speed | `number` | `1` | Multiplies the drift. |
| maxDpr | `number` | `2` | Upper limit on device pixels per CSS pixel. |
| settleAfter | `number` |  | Seconds until the drift eases to rest. Set it at 5 or less where nothing else can pause the motion. |

## When to use

- A hero that tells the story of too many tabs becoming the few that matter
- A scroll section where progress follows a scrub and ends on a calm grid
- A background with one action that resolves it, such as a tidy up button

## When not to use

- Behind long text: the loose tabs cross the whole field
- For real content: the tabs are drawn, so nothing in them can be read or clicked
- More than once or twice on a page: browsers cap live WebGL contexts near 16

## Notes for agents

- Pass a ref as progress and tween its current value. Passing a number re-renders the parent on every change
- The element needs a height. The grid sits inside its padding, so use padding classes to leave room for a heading
- Under reduced motion it always draws the finished grid, whatever progress says, so never rely on progress 0 being visible
- The component imports no GSAP and declares none. Only the demos do, so copying them means installing gsap and @gsap/react from npm under GSAP's own licence
- It has its own small WebGL setup rather than ShaderCanvas, because it draws 40 quads, and keeps the same behaviour: aria-hidden canvas, pauses off screen, follows the theme, CSS grid fallback

## The maths

- Forty tabs are 40 × 6 = 240 vertices in one buffer and one draw call, so a pixel only runs the signed distance for the tab that covers it.
- Each foot is a square minus a circle, max(square, r - |p - c|), tangent to the side and the baseline, which is the concave flare of the logo.
- Home positions follow the R2 sequence, frac(0.5 + n × 0.7549) and frac(0.5 + n × 0.5698), so the first ten tabs are spread evenly over the field.
- A loose tab's half width is √(0.24 × area / 40 × 3) / 2 for a 3:1 tab, so forty tabs cover a quarter of the box at any size.

Docs: https://ui.goodtabs.co/components/tab-field