Pin List
betaA list split into Pinned and the rest, where pressing a row's pin glides it across to the other section while everything else reflows, and an emptied section folds away.
- Data
- 1 file
- 2 npm packages
- Accessibility notes
- Animates with motion
Loading the example…
pnpm dlx @dowel-ui/cli add pin-listnpm packages installed: class-variance-authority, motion.
import { PinList } from "@/components/ui/pin-list";Default
Plain
Nothing Pinned
Controlled
<PinList>
| Prop | Type | Default |
|---|---|---|
items (required) | PinListItem[] | — |
announceWhat the status region says after a change. | (item: PinListItem, pinned: boolean) => string | defaultAnnounce |
defaultPinnedIds pinned at first render (uncontrolled). | string[] | [] |
disabledDisables every pin button. | boolean | false |
onPinnedChange | (pinned: string[]) => void | — |
pinLabelThe pin button's name. Its pressed state says whether it is pinned. | (item: PinListItem) => string | defaultPinLabel |
pinnedIds of pinned items (controlled). | string[] | — |
pinnedLabelHeading of the pinned section. | ReactNode | "Pinned" |
unpinnedLabelHeading of the rest. | ReactNode | "All" |
variant | "card" | "plain" | "card" |
Plus every attribute of <div> except children, defaultValue.
Two real lists (ul, role=list), each named by its visible section label through aria-labelledby. Every row has one control, a pin toggle exposed with aria-pressed and named "Pin <title>" (overridable through pinLabel), so its state is announced as pressed rather than by a changing name. A moved row is re-created in its new section and focus is put back on its pin button there, so pressing again undoes it. A polite, visually hidden status region announces each change ("Pinned Inbox" / "Unpinned Inbox", overridable through announce) and is empty on first paint. Row glyphs are aria-hidden. The pin's tilt and fill are a second signal alongside the section it sits in, never the only one. Under reduced motion rows jump to their new place and sections appear and disappear without animating.
8/8
checks passed
- Tested — passes
- axe assertion — passes
- Keyboard tested — does not apply
- Storybook examples — passes
- Accessibility documented — passes
- Semantic tokens only — passes
- Motion from tokens — passes
- className merged — passes
- Visible focus — does not apply
- No fixed widths — passes