# Spring sheet (goodtabs UI)

A dialog sheet that hands your release velocity to a spring and projects its rest.

## Install

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

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

## Usage

```tsx
import { SpringSheet } from "@/components/ui/spring-sheet"

<SpringSheet trigger={<Button>Open</Button>} title="Title" description="One line.">
  Body
</SpringSheet>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| trigger | `ReactElement` |  | The control that opens the sheet. |
| title | `string` |  | The accessible title. |
| description | `string` |  | One line under the title, also the accessible description. |

## When to use

- Mobile style sheets that should respond to a flick
- Teaching how velocity, projection and rubber banding fit together

## When not to use

- Long scrolling content: the handle drag conflicts with a scrolling body
- Desktop only flows where a centred dialog fits better

## Notes for agents

- Built on the Radix Dialog primitive, so focus is trapped, Escape closes, and focus returns to the trigger
- Keyboard path: Tab to the handle and press the down arrow, Enter or Space to close
- Under reduced motion it opens and closes instantly and a released drag snaps back with no spring
- The readout line is aria-live and is meant for teaching, delete it in production

## The maths

- Projected rest = y + v × rate / (1 - rate) with rate 0.998, so v × 499 pixels.
- Rubber band offset = (1 - 1 / (d × 0.55 / D + 1)) × D, where D is the sheet height.
- The sheet closes when the projected rest passes D / 2, otherwise a spring (stiffness 300, damping 30, ζ 0.87) takes the velocity back to 0.

Docs: https://ui.goodtabs.co/components/spring-sheet