SelectBox
A dropdown. Options cascade in one after another, the highlight slides to whichever option you point at, and committing rolls the trigger's label in the direction the selection moved.
Usage
<SelectBox
defaultValue="c1"
spring="smooth"
stagger={26}
options={[
{ value: "c1", label: "Category 1" },
{ value: "c2", label: "Category 2" },
]}
/>How the motion works
Panel open-progress is a spring, so an interrupted close reverses from wherever it had reached instead of restarting. Each option carries a transition delay proportional to its index, producing the cascade; on close the delays are removed so the panel dismisses as one piece rather than unwinding slowly. The highlight is a single body whose top edge and height are separate springs rather than a background toggled per option, so moving down the list reads as one object being chased — and because it is a spring it can be caught mid-flight and redirected. It runs on a slacker, slower spring than the panel does — the panel is a surface appearing and wants to be quick, while the highlight is an object crossing a distance the eye is meant to follow. It stretches along its axis in proportion to its own velocity, splitting the extra length across both edges so the centre keeps tracking the target, and settles back as it arrives. The ends of the panel are solid: the spring is left free to overshoot, since the momentum is what makes the move feel thrown, but the drawn body is clamped inside the first and last options, so landing on an end compresses the highlight against the wall — floored so it can never be crushed away — rather than sliding out of the panel. Opening places the highlight on the current selection outright in the same update that opens the panel, since sliding in from wherever the pointer last left it would read as a move the user never made. The trigger's label is the same directional swap the counter uses: the sign of the index change decides whether the old label is pushed up or down, and the label's width springs between the measured widths so the text never jumps.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| options | SelectOption[] | — | Each has value, label and optional disabled. |
| value / defaultValue | string | — | Controlled or initial selection. |
| onChange | (value: string) => void | — | Fires on commit. |
| stagger | number | 26 | Delay between option entrances, in ms. |
| placeholder | string | "Select…" | Shown when nothing is selected. |
