Typed, accessible, theme-aware React/Tailwind components installable by the shadcn CLI or your AI coding agent. Free atoms plus composed Pro blocks.
A table of contents for the page the reader is on, with the section they are currently reading highlighted as they scroll. Use it for the "On this page" rail beside documentation and guides, API references, changelogs and release notes, long blog posts and tutorials, handbooks, legal and policy pages, and reports. Common asks it answers: "table of contents component", "toc sidebar", "on this page nav", "scrollspy", "scroll spy in React", "highlight the active heading while scrolling", "docs right rail", "anchor link navigation", "in-page navigation", "sticky table of contents", "MDX toc", "react-scrollspy alternative". shadcn/ui ships nothing for this, and its navigation-menu and sidebar are for moving between pages, not around one. You pass the headings in as items — the shape rehype-slug, MDX and Contentlayer pipelines already hand you — so the list is rendered on the server and the links work before, and without, JavaScript; only the highlight needs the client. The awkward part is deciding which heading counts as current, and this fixes the three ways a hand-rolled one gets it wrong. The last section is normally shorter than the viewport, so its heading never reaches the activation line and the final entry can never light up — reaching the bottom of the scrollable area selects the last heading, because there is nothing further to read. A section stays current while it is being read rather than only while its heading is on screen, which is where an IntersectionObserver checking is-it-visible goes blank on any section taller than the window. And clicking an entry starts a scroll lasting hundreds of milliseconds, during which every heading it travels past would light up in turn, leaving the entry you clicked as the one thing not highlighted; the list holds your choice until the scroll settles, and hands control straight back if you grab the page mid-flight. offset clears a sticky site header, both for where a click lands and for where the current section begins, since the browser's own fragment jump puts the heading underneath it. It re-measures on resize and once web fonts have loaded, follows a nested scroller when the app shell scrolls an inner element instead of the window, and honours prefers-reduced-motion. The active entry is marked with aria-current="location" rather than colour alone, so it is announced and not merely seen; because clicking has to preventDefault to apply the offset, focus is moved to the heading the way the browser would have, so a keyboard reader lands in the section instead of carrying on down the contents. Modifier and middle clicks are left alone, so opening a section in a new tab still works.
uiA complete toast / notification system in one file: call `toast()` — or `toast.success` / `.error` / `.info` / `.warning` / `.loading` / `.promise` — from anywhere in your app, and render a single `<Toaster />` at the root. No provider, no context, nothing to wire up. Use it for save and delete confirmations, form submission results, copy-to-clipboard feedback, async job status, optimistic updates that may fail, undo prompts, connection lost / restored notices, rate-limit and validation errors — any "it worked" or "it failed" message that should not interrupt what the user is doing. Common asks it answers: "toast notification react", "toast component shadcn", "snackbar component", "notification popup", "flash message", "alert toast", "undo toast with action button", "promise toast for async requests", "loading toast that turns into success", "toast without a provider", "toast from outside a component", "sonner alternative", "react-hot-toast alternative", "react-toastify alternative", "notification system react", "show a message after form submit". `toast.promise(request, { loading, success, error })` moves one toast through all three states in place, so an async call needs a single line instead of a chain of manual dismissals — `success` and `error` also accept a function, so the final message can quote the resolved value or the thrown error. Every toast takes a `description`, an `action` button (the undo affordance), a `duration` where `Infinity` pins it until dismissed, and an `id` you can reuse to update a toast already on screen. `<Toaster />` takes any of six positions. The queue lives outside React through useSyncExternalStore, so a toast can be fired from an event handler, a fetch or axios interceptor, a route guard, or a plain module — the places a hook-based API cannot reach, and the usual reason a toast library ends up wrapped in a context that has to be threaded everywhere. The details a rushed implementation drops: auto-dismiss pauses while the pointer is over the stack or focus is inside it, so a toast cannot vanish mid-sentence or while a keyboard user is reaching for its action button, and it stays paused across a promise's loading → success swap. Errors announce assertive and everything else polite, so a failure is not queued behind three success messages. Swipe-to-dismiss on touch, and enter / exit animations that respect prefers-reduced-motion. Official shadcn/ui no longer ships a toast of its own — it points at sonner, an npm dependency you do not control. This is one file you own and can edit, styled with your own theme tokens, whose only package import is lucide-react (already present in a shadcn project).
uiThe nested list you can open, close and walk with the arrow keys: a file explorer or file tree, a folder or directory tree, a category or taxonomy picker, an org chart, a JSON or API-schema browser, a docs sidebar with nested sections. Common asks it answers: "tree view", "tree component", "file tree", "folder tree", "directory tree", "file explorer sidebar", "nested list with expand/collapse", "collapsible tree", "recursive tree from JSON", "expandable folder list", "VS Code-style explorer", "category tree", "org chart tree". shadcn/ui ships no tree of any kind — its collapsible is one open/closed section and its sidebar nests menus without the tree semantics — so this gets hand-rolled every time, and the part that gets dropped is always the keyboard. Pass a `data` array of `{ id, label, children?, icon? }`: a node with a `children` array is a parent (an empty array is an empty folder, which still opens), a node without one is a leaf. Open state and selection are each controlled (`expandedIds` / `selectedId` plus `onExpandedChange` / `onSelect`, which hands you the whole node) or uncontrolled (`defaultExpandedIds` / `defaultSelectedId`), so it drops into a router-driven sidebar or runs on its own. It is the real ARIA tree pattern, not a pile of nested collapsibles: role=tree / treeitem / group with aria-expanded, aria-selected and aria-level/posinset/setsize, and a roving tabindex so the whole tree is one Tab stop instead of one stop per row. Up/Down walk only the rows actually on screen, Right opens a parent and then steps into it, Left closes it or jumps out to the parent, Home/End hit the ends, Enter/Space select, and type-ahead jumps to the next row starting with what you typed (repeat a letter to cycle). Three details that are easy to get wrong are handled: closing a subtree that contains the focused row hands focus back to the row being closed instead of dropping it on <body>; the row is named by its own label via aria-labelledby, because a treeitem owns its child group and a name computed from contents would read the entire subtree as one row's name; and the disclosure arrow is a click target rather than a nested <button>, since a treeitem must not contain its own focusable elements. Renders folder/file icons by default (`showIcons={false}` for category or org trees), `indent` sets the per-level offset, and per-node `icon` overrides a single row. Styled with shadcn tokens (accent, muted-foreground, ring) so it follows light and dark themes; lucide-react is the only dependency, with no Radix and no state library. Distinct from command-palette, which is a flat searchable launcher: this is for structure you navigate rather than a name you already know.
uiThe confirmation step in front of an irreversible action: the user has to type the resource's own name ("acme-prod") before the destructive button turns on. Use it wherever a misclick would be unrecoverable — deleting a project, repository, workspace, organisation, cluster, database, or environment, removing a team member, revoking an API key, wiping data, cancelling a subscription, or any "danger zone" section of a settings page. Common asks it answers: "type to confirm", "type the project name to delete", "type DELETE to confirm", "confirm delete by typing name", "GitHub-style delete confirmation", "danger zone dialog", "destructive action modal", "disable the delete button until the name matches". shadcn/ui ships alert-dialog as an empty shell — the typed match, the disabled-until-it-matches wiring, and the announcement are left to you every time; this packages them into one drop-in that sits inside your existing dialog or card, so nothing here assumes which official components you have installed. Pass `phrase` (the name) and `onConfirm`; both sides are trimmed before comparing, so a pasted name that picked up a trailing space still matches, and an empty phrase never matches, which stops an untouched field from arming a delete. Set `caseSensitive={false}` to let "delete" pass for "DELETE", and mirror your mutation with `pending` to lock the field and swap the button label. The field opts out of autocomplete, autocorrect, autocapitalisation, and spellcheck — on a phone the first letter would otherwise be capitalised and an exact match made impossible to type. The button is genuinely disabled rather than aria-disabled, which screen readers skip: nothing is lost by that, because the label states what to type, the description explains that the button is waiting for it, and an always-mounted live region announces the moment it turns on (a live region inserted together with its text is not reliably announced, so one that appeared only on match would swallow that update). It renders as a real <form>, so Enter submits and, inside a dialog, focus lands on the field on open with no extra wiring. Styled with shadcn tokens (border-input, destructive, muted-foreground, ring) for automatic light/dark theming, with zero dependencies beyond your cn util. Distinct from confirm-button, which is a two-step click for cheap, reversible actions; this is the high-friction guard for the ones you cannot take back.
uiThe list of files under a dropzone or file picker — one row each with the file name, its size, a progress bar while it uploads, an error with a retry button when it fails, and an X to drop it from the queue. Use it on any screen that accepts files: an attachment picker, an image or avatar upload, a CSV/spreadsheet import step, a document or PDF upload, a bulk media drop, or an import wizard. Common asks it answers: "file upload list", "upload queue", "show selected files with progress", "file list with remove button", "upload progress bar per file", "attachment list", "retry failed upload", "Dropbox/Gmail-style upload rows". shadcn/ui ships no file upload of any kind, and its progress primitive is a single bar with no notion of a file, so the row layout, the byte formatting, the per-file progress and the failure affordance are hand-rolled every time. It pairs with the file-dropzone component, which hands you a File[] and deliberately stops there: this is the half that shows what happened to those files. Pass an `items` array of `{ id, name, size?, status, progress?, error? }` where status is pending | uploading | done | error; omit `progress` and the bar goes indeterminate for uploads with no known length, and an empty array renders nothing so you can mount it unconditionally next to your queue state. It is presentational on purpose and never uploads anything — you keep the requests, the concurrency, the cancellation and the retry policy, and pass `onRemove`/`onRetry` to get the buttons. Accessibility is where a queue usually goes wrong and this one is built around it: progress sits in a role=progressbar, which is not a live region, so a file crawling from 1% to 100% does not narrate every tick; instead an always-mounted role=status region announces only the rows that just finished or just failed, batched into one message per change; the first render is treated as the starting state, so a list that mounts with finished rows stays silent; and every remove/retry button carries the file name in its accessible name, because a column of buttons all called "Remove" is unusable without sight of the row. Sizes are formatted to KB/MB/GB with tabular numerals, long names truncate with a title tooltip, and it is styled with shadcn tokens (muted-foreground, destructive, primary, accent, ring) so it follows light and dark themes; lucide-react is the only dependency. Distinct from save-status, which is a one-line indicator for a single background save, and from progress-ring, which is one circular meter: this is the multi-file queue.
uiA long list that only puts the rows you can see into the DOM: five thousand rows render as about thirty nodes, so the page stops taking seconds to paint and scrolling stops stuttering. Reach for it on an admin table or data grid, a log, audit or event viewer, chat and message history, search results over a big local array, a file or asset browser, a select with thousands of options, or any list where you already hold every row in memory. Common asks it answers: "virtual list react", "virtualized list", "windowing", "react-window alternative", "react-virtualized alternative", "TanStack Virtual without the wiring", "render 10000 rows react", "long list is slow to render", "list virtualization with dynamic row heights", "variable height virtual list", "scroll performance long list", "only render visible items". shadcn/ui has no virtualization at all — its table renders every row you hand it — so this gets wired up by hand against TanStack Virtual or react-window each time, and the same four things break. Focus survives here: the row you tabbed into stays mounted after it scrolls out of the window, instead of being unmounted under you and dropping focus to the top of the page. Screen readers get the real position, because every row carries aria-posinset and aria-setsize — "item 4,213 of 5,000", not a count of the handful that happen to be mounted — and the spacer that holds the scroll height is marked presentational so the list and its items stay related. The view does not jump: rows are measured as they mount with a ResizeObserver, and when a row above the viewport turns out taller than the estimate, or older rows are prepended, the scroll offset is corrected against a row-keyed anchor in a layout effect, before the browser paints. That anchor is why prepending older chat messages keeps the message you were reading exactly where it was. And positions can be restored, via defaultScrollOffset plus a ref handle with scrollToIndex(index, "auto" | "start" | "center" | "end"), scrollToOffset and getScrollOffset. Rows may be any height and nothing has to be declared up front; estimateItemHeight (default 48) is only the guess used before a row has been measured, and overscan (default 4) sets how many rows are kept mounted beyond the edges. Controlled by count plus a render function — children is called with an index, so the data can live anywhere — with itemKey for stable identity, onScroll and empty. Defaults to role list/listitem; pass role="listbox" and itemRole="option" when the rows are selectable. Set the height with className (the default is h-72); rows are absolutely positioned, so give them padding rather than a vertical margin. Vertical only, and find-in-page reaches mounted rows only, which is inherent to windowing. Styled with shadcn tokens so it follows light and dark themes, and it ships with no dependencies at all — no Radix, no virtualization library.
ui