Pinned list
FreeItems you can pin to the top. Pinning moves the row up into Pinned and back again, so the eye follows it, and focus stays on the same pin button.
pnpm dlx shadcn@latest add https://caliperui.site/r/pinned-list.jsonPinned1
Saved views4
When to use #
Use it for
- Saved views, channels, projects or documents.
- Lists where a few items matter more to each person.
Skip it when
- Shared ordering that everyone sees; use the sortable list.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/pinned-list.json
Adds its 5 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { FileText, Hash, Inbox, Rocket, Users } from "lucide-react" import { useState } from "react" import { PinnedList, type PinnedItem } from "@/components/ui/pinned-list" const views: PinnedItem[] = [ { id: "mrr", title: "MRR by plan", meta: "Revenue · updated 2 min ago", icon: <Hash /> }, { id: "churn", title: "Churned this month", meta: "Customers · 14 accounts", icon: <Users />, pinned: true }, { id: "inbox", title: "Unassigned tickets", meta: "Support · 32 open", icon: <Inbox /> }, { id: "launch", title: "Launch checklist", meta: "Projects · 6 of 9 done", icon: <Rocket /> }, { id: "invoices", title: "Overdue invoices", meta: "Billing · $18,240", icon: <FileText /> }, ]; export function PinnedListDemo() { const [items, setItems] = useState(views); return ( <PinnedList className="w-full max-w-md" label="Saved views" items={items} onPinnedChange={(id, pinned) => setItems((list) => list.map((i) => (i.id === id ? { ...i, pinned } : i)))} /> ); }
API reference #
PinnedList
component
Accessibility #
It exposes status semantics, with aria-hidden, aria-pressed and aria-label kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.