{ "$schema": "https://ui.shadcn.com/schema/registry.json", "name": "pulld", "homepage": "https://pulld.dev", "items": [ { "name": "copy-button", "type": "registry:ui", "title": "Copy Button", "description": "Accessible icon button that copies a string to the clipboard and shows a transient copied state. Has aria-label, an aria-live announcement, keyboard focus ring, and a configurable reset timeout. Use it next to code blocks, API keys, share links, or any inline value a user might want to copy.", "dependencies": [ "lucide-react" ], "files": [ { "path": "registry/ui/copy-button.tsx", "type": "registry:ui" } ] }, { "name": "kbd", "type": "registry:ui", "title": "Kbd", "description": "Inline keyboard key rendered as a real element and styled as a bordered monospace keycap. Use it wherever an interface names a key the reader is meant to press: command palettes and ⌘K hints, tooltips, menu item accelerators, empty states that suggest a shortcut, onboarding tours, documentation and changelogs, and keyboard-shortcut help sheets. Common asks it answers: \"kbd component\", \"keyboard shortcut badge\", \"render Cmd+K\", \"hotkey chip\", \"keycap style\", \"show a keybinding in a tooltip\", \"⇧⌘P badge\", \"shortcut hint next to a menu item\", \"Ctrl+S indicator\". shadcn/ui now ships a kbd of its own, so choose deliberately rather than by search rank. Theirs is a flat sans-serif chip with no border at text-xs, and it comes with a KbdGroup wrapper for multi-key sequences, a rule that shrinks icons placed inside it, and one that inverts its colours inside a tooltip — if you want any of those, take theirs. This one is a single element with a border, monospace text at 10px and slightly wider padding, so it reads as a physical key rather than as inline text and stays legible against surrounding prose at small sizes. Both are dependency-free, use the semantic tag so assistive technology announces the content as keyboard input, and theme through shadcn tokens. For several keys in a row, wrap them in a flex container with a gap yourself, or install keyboard-shortcuts, which composes this into a grouped help sheet opened with ?.", "dependencies": [], "files": [ { "path": "registry/ui/kbd.tsx", "type": "registry:ui" } ] }, { "name": "empty-state", "type": "registry:ui", "title": "Empty State", "description": "The centred placeholder a screen shows when it has nothing to draw — a dashed panel with an optional icon, a heading, one line of explanation, and room for a call to action. Use it for an empty table, list, inbox, or feed, a search or filter that matched nothing, a workspace, project or team before its first item exists, a first-run or onboarding screen, a dashboard card with no data yet, an empty cart, folder, or notification tray. Common asks it answers: \"empty state\", \"no results found\", \"zero state\", \"blank slate\", \"no data placeholder\", \"nothing here yet\", \"empty list or table component\", \"empty search results\", \"first run experience\", \"no items yet with a create button\". shadcn/ui now ships an `empty` of its own, so choose deliberately rather than by accident: theirs is a six-part compound API (Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent) that composes into any arrangement and pulls in class-variance-authority; this is the one-import version — title plus optional icon, description and action, four props in total and no dependencies at all — for the much more common case where every empty state in the app looks alike and assembling six elements at each call site is just ceremony. Two accessibility details differ as well, and they are the two most often got wrong: here the title renders as a real h3, so it joins the heading outline and screen-reader users can reach it with heading navigation, whereas the official EmptyTitle is a styled div that heading navigation cannot see; and the icon wrapper is marked aria-hidden, because it is decoration, and announcing \"inbox\" or \"circle-slash\" before the sentence that actually explains the situation is noise. The description is capped at max-w-sm so the line keeps a readable measure inside a wide table. It has no hooks and no event handlers, so it carries no \"use client\" and renders inside a React Server Component without pulling a client boundary in behind it; you pass your own icon element, so it adds no icon library. Styled with shadcn tokens (border, muted-foreground) for light and dark themes. Distinct from skeleton and spinner, which say the rows are still loading: this one says the rows are not coming until the user does something.", "dependencies": [], "files": [ { "path": "registry/ui/empty-state.tsx", "type": "registry:ui" } ] }, { "name": "stat-card", "type": "registry:ui", "title": "Stat Card", "description": "The single-number tile at the top of a dashboard: a label, one big value, and an optional percentage change with an up or down arrow — green when the number moved the right way, red when it did not. Use it wherever a screen opens with a row of headline figures: an analytics or metrics dashboard, an admin overview, a KPI or scorecard row, a billing and usage summary, a SaaS home screen, a revenue or traffic report. Common asks it answers: \"stat card\", \"metric card\", \"KPI card\", \"dashboard stat tile\", \"number card with percentage change\", \"revenue card with trend arrow\", \"analytics summary cards\", \"stats row\", \"show total users with growth\", \"Stripe/Vercel-style dashboard tiles\". shadcn/ui ships card as an empty container with no notion of a metric, so the value typography, the delta colouring and the arrow are hand-rolled on every dashboard. Pass `label`, `value` and optionally `delta` (a number: positive renders the up arrow, negative the down arrow, and omitting it renders no delta at all) plus a `hint` line for the comparison period, e.g. \"vs. last month\". `value` is a ReactNode, not a string, so a pre-formatted currency or an Intl.NumberFormat result drops straight in and the component never guesses at your locale or currency. The direction is not left to colour alone: the arrow is aria-hidden and an sr-only \"Up\"/\"Down\" is spoken before the number, so the tile still means something to a screen reader and to a red-green colour-blind reader, which a bare green percentage does not. Composes into a responsive grid to form the stats row, and pairs with gauge and progress-ring when the figure is a ratio rather than a total. Styled with shadcn tokens (card, muted-foreground) with an explicit dark-mode pair for the delta colours; lucide-react is the only dependency. Distinct from feature-card, which sells a capability with an icon and copy: this one carries a live number.", "dependencies": [ "lucide-react" ], "files": [ { "path": "registry/ui/stat-card.tsx", "type": "registry:ui" } ] }, { "name": "theme-toggle", "type": "registry:ui", "title": "Theme Toggle", "description": "The one-button light/dark switch you drop in a navbar, header or settings row — click it and the whole app flips theme, and the choice survives a reload. Common asks it answers: \"dark mode toggle\", \"theme toggle button\", \"light dark switcher\", \"toggle dark mode in Tailwind\", \"sun moon toggle\", \"dark mode without next-themes\", \"theme switcher for shadcn\", \"remember the user's theme\", \"respect the system theme\". shadcn/ui has no installable toggle: its dark-mode guide hands you a next-themes provider to wire up and a dropdown to assemble yourself, so a plain button is written by hand every time. This is that button — one file, no provider, no context, no next-themes and no extra package. It toggles the `dark` class on the html element, which is exactly what Tailwind's class dark mode and the shadcn tokens already read, so it works with the theme you have rather than introducing another one. On first load it reads the saved choice from localStorage and falls back to the OS `prefers-color-scheme`, so a first-time visitor gets their system theme and a returning one gets their own; every later click writes the choice back. That first read happens in an effect rather than during render, because `window` does not exist on the server and an inline branch would either crash SSR or hydrate to different markup than it sent — which also means the theme is applied just after first paint, so add the usual one-line script in your document head if you need to kill the flash on a static page. The Sun/Moon swap is done with the `dark:` variant rather than JS state, so the icon matches the document even if something else on the page changes the theme. It is a real button that forwards every button prop (className, id, onClick, disabled), carries an aria-label and an aria-pressed that reflects the current mode, hides both icons from screen readers, and has a focus-visible ring; styling uses shadcn tokens (accent, muted-foreground, ring, border) so it matches your other icon buttons. lucide-react is the only dependency.", "dependencies": [ "lucide-react" ], "files": [ { "path": "registry/ui/theme-toggle.tsx", "type": "registry:ui" } ] }, { "name": "avatar-stack", "type": "registry:ui", "title": "Avatar Stack", "description": "Row of overlapping circular avatars — a facepile — that collapses everything past max into a \"+N\" badge. Reach for it wherever a set of people is shown on one line: team members on a project card, assignees or reviewers on an issue or pull request, meeting attendees, who is online or currently viewing a document, a 'shared with' list, participants in a thread, or contributors on a repo. Common asks it answers: \"avatar group\", \"avatar stack\", \"facepile\", \"overlapping avatars\", \"stacked profile pictures\", \"user avatars in a row\", \"+N more avatars\", \"avatar overflow count\", \"assignee avatars\", \"who is online avatars\", \"team member avatars\", \"participant avatars\". shadcn/ui ships a single avatar and no way to group them, so the two fiddly parts get rebuilt every time: the negative margin that overlaps the circles, and the ring in the *background* colour on each one that keeps the overlap legible on a card, a table row or a dark surface. Pass avatars as an array of { src?, alt } plus max (default 4); an entry with no src falls back to the first letter of its alt, and every avatar keeps its alt as its accessible name, so a screen reader reads the people rather than a row of unlabelled images. Plain img and Tailwind tokens: unlike the official avatar it pulls in no Radix package, and it has no dependencies at all.", "dependencies": [], "files": [ { "path": "registry/ui/avatar-stack.tsx", "type": "registry:ui" } ] }, { "name": "password-input", "type": "registry:ui", "title": "Password Input", "description": "Password text input with a built-in show/hide toggle button. Toggles between password and text, has an accessible aria-label and aria-pressed, and the toggle stays out of the tab order. Use it in sign-up, login, and reset-password forms.", "dependencies": [ "lucide-react" ], "files": [ { "path": "registry/ui/password-input.tsx", "type": "registry:ui" } ] }, { "name": "spinner", "type": "registry:ui", "title": "Spinner", "description": "An inline loading indicator that announces itself: a spinning lucide Loader2 inside a role=\"status\" live region with a screen-reader-only label, so a pending operation is heard as well as seen. Reach for it while fetching data, submitting a form, loading a page or a section, as a Suspense or lazy-route fallback, beside a disabled control, inside a table cell or panel that is still filling in, or anywhere you would otherwise drop a bare \"Loading…\" string. Common asks it answers: \"loading spinner\", \"react spinner component\", \"loader component\", \"busy indicator\", \"activity indicator\", \"throbber\", \"accessible loading state\", \"aria-live loading announcement\", \"screen reader loading\", \"Suspense fallback spinner\", \"spinning Loader2\", \"animate-spin loader\". The usual inline version — a bare Loader2 with animate-spin dropped straight into the markup — is invisible to assistive technology: the icon is decorative, so nothing is announced and a screen-reader user waits in silence with no idea anything is happening. Here the icon is aria-hidden and the announcement comes from an sr-only label instead, \"Loading\" by default; set label to say what is loading (\"Loading invoices\") so the same component can announce something useful on every screen. It is 1rem square and inherits the current text colour, so it sits correctly inside a button, a link, or a line of muted text with no extra styling, and every span prop (id, style, className, data-*) passes straight through to the wrapper. Pick the sibling that matches the shape: loading-button for a button whose own label swaps to a busy state, progress-ring or gauge when the percentage is known — this is the indeterminate \"something is happening\" case. Styled with shadcn tokens for light and dark themes; lucide-react is the only dependency.", "dependencies": [ "lucide-react" ], "files": [ { "path": "registry/ui/spinner.tsx", "type": "registry:ui" } ] }, { "name": "code-block", "type": "registry:ui", "title": "Code Block", "description": "Read-only code snippet in a bordered, scrollable panel with a copy button that fades in on hover or keyboard focus and an optional uppercase language label in the corner. Reach for it whenever a page has to show code the reader will copy rather than edit: install and CLI commands in a README or docs site, curl and SDK examples in API reference pages, config file snippets, error output or stack traces, migration and changelog before/after blocks, an onboarding \"paste this into your terminal\" step, or the code sample on a developer landing page. Common asks it answers: \"code block\", \"code snippet component\", \"copy code button\", \"pre code with copy\", \"terminal command block\", \"docs code sample\", \"shadcn code block\", \"snippet with language label\", \"copyable command\". shadcn/ui has no code block at all, so this normally gets rebuilt from a bare
 plus a hand-wired clipboard button that forgets the copied state and never reveals itself to a keyboard user — here the button is inside focus-within, so tabbing to it makes it appear, and the copied confirmation is announced (it composes pulld's copy-button). Deliberately a container, not a highlighter: pass a `code` string and it renders semantic 
 with `data-language` set, so it costs no bundle and stays out of the way if you later pipe in shiki, Prism, or highlight.js — unlike react-syntax-highlighter, which drags a whole grammar bundle in for a snippet you only wanted to display. Long lines scroll horizontally instead of wrapping. Extra props land on the wrapper div. No dependencies beyond your cn util.",
      "dependencies": [],
      "registryDependencies": [
        "copy-button"
      ],
      "files": [
        {
          "path": "registry/ui/code-block.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "loading-button",
      "type": "registry:ui",
      "title": "Loading Button",
      "description": "Button that shows a spinner and disables itself while an async action is pending. Set loading=true during submit, save, or checkout to prevent double-clicks; an optional loadingText swaps the label. Composes pulld's spinner atom.",
      "dependencies": [],
      "registryDependencies": [
        "spinner"
      ],
      "files": [
        {
          "path": "registry/ui/loading-button.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "confirm-button",
      "type": "registry:ui",
      "title": "Confirm Button",
      "description": "Two-step confirm button for destructive actions: the first click arms it and swaps the label to a confirmation prompt, the second click runs onConfirm. Auto-reverts after a timeout and on blur so a stray click can't fire. Use it for delete, remove, reset, revoke, unsubscribe, or disconnect actions where a full alert dialog is overkill. Theme-aware via shadcn destructive tokens; no dependencies.",
      "dependencies": [],
      "files": [
        {
          "path": "registry/ui/confirm-button.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "command-palette",
      "type": "registry:ui",
      "title": "Command Palette",
      "description": "A ready-made ⌘K command palette: one component you drop in, open with a keyboard shortcut, and fill with actions. Use it for global search, jump-to-page navigation, quick actions and power-user shortcuts in dashboards, admin panels, editors, docs sites and any app that has outgrown its nav bar. Common asks it answers: \"command palette\", \"cmd+k menu\", \"ctrl+k search\", \"command menu\", \"quick switcher\", \"spotlight-style search\", \"raycast-style launcher\", \"jump to anything\", \"action launcher\", \"global search dialog\", \"cmdk alternative\", \"command palette without cmdk\". shadcn/ui does ship command, and the difference is what you get handed: that one is nine primitives — Command, CommandDialog, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, CommandSeparator, CommandShortcut — wrapping the cmdk npm package and pulling in the dialog item, which you then assemble into a palette yourself. This is a single component that depends on nothing but lucide-react: no cmdk, no dialog, no assembly. It arrives with the parts that are otherwise left to you — recently used entries surfaced when the input is empty, fuzzy filtering that highlights the matched characters in each result, grouped sections, wrap-around arrow-key navigation, and an async source hook so results can come from your own endpoint instead of a hard-coded array. The fiddly part of a palette is not the list, it is the focus: opening it traps focus so Tab cannot wander into the page behind, closing it puts focus back on whatever the reader was on, and the highlighted row is exposed with combobox and listbox roles plus aria-activedescendant, so the active option is announced while the text cursor stays in the input where typing belongs. If you would rather not run search infrastructure, the exported pulldSearchSource helper points the same async source at pulld Search for hosted semantic results.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/command-palette.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "toast",
      "type": "registry:ui",
      "title": "Toast",
      "description": "A production-ready toast/notification system: call toast(), toast.success/error/info/warning/loading or toast.promise() from anywhere — no provider or context needed — and render one . Auto-dismiss with pause-on-hover and focus, swipe-to-dismiss, six positions, action buttons, and accessible live regions (assertive for errors, polite otherwise). Enter/exit animation respects prefers-reduced-motion. Use it for success/error feedback, async operation status, and undo prompts.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/toast.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "search-input",
      "type": "registry:ui",
      "title": "Search Input",
      "description": "Search field with a leading search icon and a trailing clear (✕) button that appears once there's text and empties the field, refocusing it. Works controlled or uncontrolled, forwards a ref so you can focus it from a keyboard shortcut (e.g. \"/\"), hides the browser's native search-cancel button, and clearing fires onChange so list/table/typeahead filtering updates. Use it to filter lists, tables, command menus, dropdowns, or any search box. Unlike a bare input it ships the icon, the clear control, and accessible focus rings out of the box. Theme-aware via shadcn tokens; depends only on lucide-react.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/search-input.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "number-input",
      "type": "registry:ui",
      "title": "Number Input",
      "description": "Number field with − and + stepper buttons that respect min, max, and step (including decimal steps), so users adjust a value without selecting and retyping. Works controlled or uncontrolled, forwards a ref, clamps at the bounds and disables the button once a limit is reached, and steps via a native input event so onChange fires and your form or state stays in sync. Use it for quantity pickers in carts and order forms, price or amount fields, numeric settings like font size, padding, or count, and any spinbutton-style input. Unlike a bare  it ships accessible, theme-aware +/− controls and hides the browser's inconsistent native spinners. Theme-aware via shadcn tokens; depends only on lucide-react.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/number-input.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "otp-input",
      "type": "registry:ui",
      "title": "OTP Input",
      "description": "One-time passcode / verification code input split into individual single-character slots (default 6). Use it for two-factor authentication (2FA), email confirmation codes, phone/SMS verification, and authenticator app codes on verify, login-challenge, and confirm-email screens. Paste a full code into any slot and it distributes across them, supports OS one-time-code autofill, auto-advances as you type, Backspace clears and steps back, and arrow/Home/End keys move between slots. Works controlled or uncontrolled, forwards a ref to focus from a shortcut, fires onComplete when the last slot fills, and can mirror its value into a hidden input for native form submit. Unlike a single text field it ships the familiar boxed code UI with accessible per-digit labels and a group label. Theme-aware via shadcn tokens; no dependencies.",
      "dependencies": [],
      "files": [
        {
          "path": "registry/ui/otp-input.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "tag-input",
      "type": "registry:ui",
      "title": "Tag Input",
      "description": "Multi-value text input that turns typed entries into removable chips — press Enter or comma to add a tag, click the × or press Backspace on an empty field to remove the last one, and paste a comma- or newline-separated list to add many at once. Use it for tags, labels, keywords, categories, email recipients, skills, or allowed domains — any free-form list of short values on a form or filter bar. Works controlled or uncontrolled via a string[] value, forwards a ref to focus the field, and supports a max count, case-insensitive de-duplication, and a validate hook to reject bad entries. shadcn/ui ships no tag or chips input; chips use secondary tokens so they follow your theme, each remove button is aria-labelled, and add/remove is announced via an aria-live region. Depends only on lucide-react.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/tag-input.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "copy-field",
      "type": "registry:ui",
      "title": "Copy Field",
      "description": "Read-only input that shows a value with a copy button docked at its right edge — one click copies, and focusing or clicking the field selects the whole value for manual copy. Use it to surface an API key, secret, access token, client ID/secret, invite or share link, webhook or endpoint URL, account/order/transaction ID, or any generated string a user reads once and copies. shadcn/ui ships no copy field; this composes pulld's copy-button (so it inherits the accessible copied-state announcement and focus ring), forwards a ref so you can focus it from a shortcut, renders the value in monospace, and is theme-aware via shadcn tokens with no extra dependencies.",
      "dependencies": [],
      "registryDependencies": [
        "copy-button"
      ],
      "files": [
        {
          "path": "registry/ui/copy-field.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "segmented-control",
      "type": "registry:ui",
      "title": "Segmented Control",
      "description": "Horizontal group of 2–4 mutually exclusive options rendered as one pill/track — a lighter switch than Tabs for toggling a view, time range, unit, sort order, or mode (e.g. List/Grid, Day/Week/Month, °C/°F, Light/Dark). Built as an accessible radiogroup: arrow keys (and Home/End) roving-focus and select, each segment is a role=radio with aria-checked, there's a focus-visible ring, per-segment disabling, and it works controlled or uncontrolled via a string value with onValueChange. Unlike Tabs it doesn't manage panels — it's only the selector — and unlike a Switch it supports more than two choices. Theme-aware via shadcn tokens; no dependencies.",
      "dependencies": [],
      "files": [
        {
          "path": "registry/ui/segmented-control.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "step-indicator",
      "type": "registry:ui",
      "title": "Step Indicator",
      "description": "Horizontal stepper that shows progress through a fixed sequence — numbered circle markers joined by a line, with each step rendered as complete (filled with a check), current (ringed and highlighted), or upcoming (muted). Use it at the top of a multi-step form, wizard, onboarding or setup flow, checkout, KYC/verification, or account-creation flow to tell users where they are and how much is left; pass a `current` index and it derives every step's state. shadcn/ui ships no stepper. Accessible as an ordered list where the active step carries aria-current=\"step\" and each marker has an sr-only status (Completed / Current step / Not completed); the check icon is aria-hidden. Optionally pass onStepClick to make already-reached steps navigable buttons (with a focus-visible ring), while upcoming steps stay non-interactive. Theme-aware via shadcn tokens with dark mode; only depends on lucide-react for the check icon.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/step-indicator.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "rating",
      "type": "registry:ui",
      "title": "Rating",
      "description": "Star rating for both input and display, with half-star support. Use it to collect a score — rate this product, leave a review, a satisfaction or feedback rating, thumbs/stars on an order or support ticket — or, in read-only mode, to show an average score next to reviews, listings, or search results (an average like 3.7 fills 70% of a star). shadcn/ui ships no rating component. Works controlled or uncontrolled via a number value with onValueChange, forwards a ref, and posts through a hidden input in native forms via `name`. Set allowHalf to pick half stars — click the left half of a star, or step by 0.5 with the keyboard. Accessible as a slider: it's focusable with a focus-visible ring, arrow keys raise and lower the score, Home clears to 0 and End maxes out, and it exposes aria-valuenow plus a spoken aria-valuetext (\"3.5 out of 5 stars\"); read-only mode renders as a labelled image instead. The empty and filled stars are decorative and aria-hidden. Configure the number of stars with max and pixel size with size. Theme-aware via shadcn tokens (filled stars use the primary color, so it follows your theme in light and dark mode); only depends on lucide-react for the star icon.",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "registry/ui/rating.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "timeline",
      "type": "registry:ui",
      "title": "Timeline",
      "description": "Vertical timeline that renders a list of events as dots on a connecting line, each with an optional time, title, and description. Use it for an activity feed, audit or history log, a changelog or release notes, order/shipment tracking, a deploy or CI/CD run log, notifications, a comment or event stream, a roadmap, or an 'about' resume/experience list — anywhere you show what happened in chronological order. shadcn/ui ships no timeline. Pass an items array (title, optional time, description, icon, and a color accent); the timestamp renders as a semantic