{ "$schema": "https://ui.shadcn.com/schema/registry.json", "name": "quill-ds", "homepage": "https://www.quilldesignsystem.com", "items": [ { "name": "quill", "type": "registry:base", "title": "Quill Design System", "description": "Warm parchment surfaces, moss accents, Fraunces display type, and rich ink tones. A handcrafted theme for editorial and creative applications.", "author": "Craftwell (https://www.quilldesignsystem.com)", "files": [ { "path": "registry/themes/quill.css", "type": "registry:base", "target": "app/quill-theme.css" } ] }, { "name": "icon", "type": "registry:ui", "title": "Icon (Material Symbols)", "description": "The Quill icon component — Material Symbols Outlined at weight 400, bundled as a self-contained core set covering every icon the Quill blocks use.", "files": [ { "path": "registry/lib/icon.tsx", "type": "registry:ui", "target": "components/ui/icon.tsx" }, { "path": "src/components/ui/icons.core.mjs", "type": "registry:ui", "target": "components/ui/icons.core.mjs" }, { "path": "registry/lib/icons.core.d.mts", "type": "registry:ui", "target": "components/ui/icons.core.d.mts" } ] }, { "name": "tone-badge", "type": "registry:ui", "title": "Tone badge", "description": "Quill's uppercase tag pill for status, tiers, and labels — wraps Badge with the pigment vocabulary (moss/gold/terracotta/indigo/neutral/muted) baked in so tone never drifts per call site.", "registryDependencies": [ "badge" ], "files": [ { "path": "registry/lib/tone-badge.tsx", "target": "components/ui/tone-badge.tsx", "type": "registry:ui" } ], "docs": "Quill's uppercase tag pill for status, tiers, and labels — wraps Badge with the pigment vocabulary (moss/gold/terracotta/indigo/neutral/muted) baked in so tone never drifts per call site. Do: Leave `solid` false (the tinted default) for regular status/tier labels — it is the visually quieter option and still hits AA contrast per tone. Don't: Set `solid` on every ToneBadge in a view — solid is reserved for the one strong cue (a \"current\" marker, a live state) and loses its signal once everything is solid. Do: Render every uppercase tag pill through ToneBadge, even one-off labels. Don't: Hand-roll a `rounded-full … uppercase` span with ad hoc tone colors — it drifts from the AA-checked tint/solid pairs and the two-size scale. Do: Use ToneBadge's own size scale — `md` (20px) or `sm` (16px, the count-pill scale). Don't: Shrink a pill by hand-tightening its tracking or padding instead of passing `size=\"sm\"` — type stays `--text-2xs` at 0.1em tracking in both sizes. Use badge when the label is a generic chip outside Quill's tag-pill vocabulary — use Badge directly instead of forcing it through a tone." }, { "name": "activity-feed", "type": "registry:block", "title": "Activity feed", "description": "A card timeline of recent events — avatar, action text, and a relative timestamp per entry.", "meta": { "intent": [ "data-display" ], "use_when": "You need to show a chronological history of what happened — recent events with who, what, and when." }, "registryDependencies": [ "card", "avatar", "badge", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/activity-feed.tsx", "type": "registry:component", "target": "components/quill/activity-feed.tsx" } ], "docs": "A card timeline of recent events — avatar, action text, and a relative timestamp per entry. Do: Use relative timestamps (\"2 hours ago\") for recent activity — they read faster than absolute dates. Don't: Force users to parse a full timestamp for something that happened minutes ago. Use notifications when the events need an unread/read state and a mark-all-read action, not just a read-only history. Use data-table when the events are really records you need to filter, sort, or act on, not just skim chronologically." }, { "name": "alerts", "type": "registry:block", "title": "Alert stack", "description": "A vertical stack of informational, success, and destructive alerts with icons.", "meta": { "intent": [ "feedback" ], "use_when": "You need to surface inline status messages of varying severity (info, success, destructive) within a page." }, "registryDependencies": [ "alert", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/alerts.tsx", "type": "registry:component", "target": "components/quill/alerts.tsx" } ], "docs": "A vertical stack of informational, success, and destructive alerts with icons. Do: Match severity to consequence: destructive for blocking errors, default for neutral information. Don't: Use destructive styling for emphasis on non-error messages. Use sonner when the message is transient feedback about an action just taken — use a toast, not an inline alert. Use announcement-banner when the message is site-wide and dismissible rather than local to a page section. Use empty-state when the \"message\" is really an empty view that needs an explanation and a call to action." }, { "name": "analytics-charts", "type": "registry:block", "title": "Analytics charts", "description": "A dashboard pair — an area chart of audience growth beside a bar chart of weekly sales, each on its own card.", "meta": { "intent": [ "data-viz", "data-display" ], "use_when": "You need a dashboard view of trends over time — an area chart plus a bar chart on card surfaces." }, "registryDependencies": [ "card", "chart", "badge" ], "files": [ { "path": "registry/blocks/analytics-charts.tsx", "type": "registry:component", "target": "components/quill/analytics-charts.tsx" } ], "dependencies": [ "recharts" ], "docs": "A dashboard pair — an area chart of audience growth beside a bar chart of weekly sales, each on its own card. Do: Color each series from the chart token set (--chart-1…--chart-5), assigned in a fixed order. Don't: Hardcode a raw accent pigment (--terracotta, --moss, --indigo…) for a series — some pairs are documented as failing colorblind-distinguishability. Do: Pair a multi-series chart with a ChartLegend, as the Audience chart does — color is tied to a series name in text, never left for the reader to infer. Don't: Ship a multi-series chart with no legend — color becomes the only signal distinguishing readers from subscribers. Use stat-cards when you need at-a-glance numbers, not a trend shape — a single KPI reads faster as a number than a chart. Use data-table when the audience needs to inspect exact values per row, not the shape of change over time." }, { "name": "announcement-banner", "type": "registry:block", "title": "Announcement banners", "description": "Two dismissible announcement banners — an inline bordered page banner and a full-bleed primary-color variant.", "meta": { "intent": [ "feedback", "marketing" ], "use_when": "You need to announce something site-wide with a dismissible banner, either inline or full-bleed." }, "registryDependencies": [ "badge", "button", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/announcement-banner.tsx", "type": "registry:component", "target": "components/quill/announcement-banner.tsx" } ], "docs": "Two dismissible announcement banners — an inline bordered page banner and a full-bleed primary-color variant. Do: Give every banner variant its own dismiss button with a specific aria-label (e.g. \"Dismiss workshop banner\"), not a generic one. Don't: Ship a banner with no way to dismiss it, or multiple banners sharing one ambiguous \"Dismiss\" label. Use alerts when the message is local to a page section and tied to system status, not a site-wide announcement. Use cookie-consent when the banner requires an explicit choice (accept/reject), not just a dismiss." }, { "name": "badge-on-card", "type": "registry:block", "title": "Badges on card", "description": "Default, secondary, and destructive badges shown together on a card surface.", "meta": { "intent": [ "data-display" ], "use_when": "You need to show status or category badges attached to a content card." }, "registryDependencies": [ "card", "badge" ], "files": [ { "path": "registry/blocks/badge-on-card.tsx", "type": "registry:component", "target": "components/quill/badge-on-card.tsx" } ], "docs": "Default, secondary, and destructive badges shown together on a card surface. Do: Reserve the destructive variant for a status that actually needs attention (blocked, failed, overdue). Don't: Use destructive styling for emphasis on a badge that isn't actually a warning. Use tone-badge when you need a badge whose color is driven by a semantic tone token rather than the three fixed variants here. Use data-table when the badges are actually per-row status in a list of records, not a single card's labels." }, { "name": "calendar-page", "type": "registry:block", "title": "Calendar page", "description": "A single-date calendar paired with the selected day's session list, in one studio-schedule card.", "meta": { "intent": [ "scheduling" ], "use_when": "You need to pick a single date and show what's scheduled that day." }, "registryDependencies": [ "card", "calendar", "badge", "separator" ], "files": [ { "path": "registry/blocks/calendar-page.tsx", "type": "registry:component", "target": "components/quill/calendar-page.tsx" } ], "docs": "A single-date calendar paired with the selected day's session list, in one studio-schedule card. Do: Keep the session list in sync with whatever date is selected in the calendar — the two panes are one control, not two independent widgets. Don't: Let the calendar selection and the listed sessions fall out of sync (e.g. a static list that ignores the picked date). Use calendar-range when the user is booking a span of days, not viewing what's scheduled on one. Use activity-feed when you want a chronological history of past events, not a schedule of upcoming sessions tied to a picked date." }, { "name": "calendar-range", "type": "registry:block", "title": "Calendar range picker", "description": "A stateful two-month range calendar for booking a span of days, with clear and reserve actions.", "meta": { "intent": [ "scheduling", "form" ], "use_when": "You need to book a span of days with a two-month range picker and confirm/clear actions." }, "registryDependencies": [ "card", "calendar", "button", "badge" ], "files": [ { "path": "registry/blocks/calendar-range.tsx", "type": "registry:component", "target": "components/quill/calendar-range.tsx" } ], "dependencies": [ "react-day-picker" ], "docs": "A stateful two-month range calendar for booking a span of days, with clear and reserve actions. Do: Keep the Clear action available whenever a range is selected, separate from Reserve. Don't: Force a user to re-click through the whole range to correct a mis-click — that's what Clear is for. Do: Disable Reserve until both a start and end date are selected. Don't: Let a half-made range (just a start date) submit as if it were a valid booking. Use calendar-page when the user is picking a single day to view, not a multi-day span to reserve. Use checkout when the range is just one field within a larger purchase flow, not the whole card." }, { "name": "chat", "type": "registry:block", "title": "Chat panel", "description": "A one-on-one messaging panel — contact header, message bubbles, and a composer input.", "meta": { "intent": [ "messaging" ], "use_when": "You need a one-on-one messaging panel with a contact header, bubbles, and a composer." }, "registryDependencies": [ "input", "button", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/chat.tsx", "type": "registry:component", "target": "components/quill/chat.tsx" } ], "docs": "A one-on-one messaging panel — contact header, message bubbles, and a composer input. Do: Align the current user's messages to one side (right) and the other party's to the other (left) — position is the primary cue for who said what. Don't: Rely on color alone to distinguish sender — pair it with consistent alignment. Use list-detail when you need a list of conversations alongside the open thread, not just the single open thread. Use notifications when the messages are one-way system alerts, not a two-way conversation with someone." }, { "name": "checkout", "type": "registry:block", "title": "Checkout", "description": "A two-column checkout — payment method picker and card form beside an order summary sidebar.", "meta": { "intent": [ "commerce", "form" ], "use_when": "You need to collect payment and card details alongside an order summary." }, "registryDependencies": [ "card", "input", "label", "button", "radio-group", "separator", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/checkout.tsx", "type": "registry:component", "target": "components/quill/checkout.tsx" } ], "docs": "A two-column checkout — payment method picker and card form beside an order summary sidebar. Do: Use a RadioGroup for payment-method choice (Card/Bank/Wallet) — it's a single mutually exclusive choice, styled as cards. Don't: Use checkboxes or a dropdown for a single mutually exclusive payment method. Do: Pair the payment submit button with a lock icon, so the action itself visibly reads as a secure transaction. Don't: Ship the payment submit button with no lock icon — nothing on the button signals that submitting card data is secure. Use order-summary when you only need the cart-review card, not the full payment form beside it. Use wizard when payment is one step in a longer multi-step flow, not a single standalone page." }, { "name": "command-palette", "type": "registry:block", "title": "Command palette", "description": "A searchable ⌘K command palette with grouped actions, recent items, and keyboard shortcuts.", "meta": { "intent": [ "navigation" ], "use_when": "You need a searchable ⌘K launcher for actions and quick navigation." }, "registryDependencies": [ "command", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/command-palette.tsx", "type": "registry:component", "target": "components/quill/command-palette.tsx" } ], "docs": "A searchable ⌘K command palette with grouped actions, recent items, and keyboard shortcuts. Do: Group items into labeled sections (Actions, Recent) so a long list stays scannable. Don't: Dump every possible command into one flat, unlabeled list. Use search-results when search results should live inline on the page, not in a global keyboard-triggered overlay. Use dropdown-menu when the list of options is small and tied to a specific trigger element, not a global searchable launcher." }, { "name": "contact-form", "type": "registry:block", "title": "Contact form", "description": "A get-in-touch card — name, email, a topic select, and a message field.", "meta": { "intent": [ "form" ], "use_when": "You need a get-in-touch form with name, email, topic, and message fields." }, "registryDependencies": [ "card", "input", "label", "button", "textarea", "select" ], "files": [ { "path": "registry/blocks/contact-form.tsx", "type": "registry:component", "target": "components/quill/contact-form.tsx" } ], "docs": "A get-in-touch card — name, email, a topic select, and a message field. Do: State a response-time expectation in the description (\"We usually reply within one working day.\") so the form doesn't feel like it disappears into a void. Don't: Leave users with no sense of whether or when they'll hear back. Use newsletter when you only need a single email field to grow a list, not a full multi-field inquiry form. Use settings when the fields are updating an existing profile, not sending a one-off message." }, { "name": "cookie-consent", "type": "registry:block", "title": "Cookie consent", "description": "A cookie consent banner offering accept, reject, and preferences actions.", "meta": { "intent": [ "feedback" ], "use_when": "You need a compliance banner offering accept, reject, and preferences choices." }, "registryDependencies": [ "button", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/cookie-consent.tsx", "type": "registry:component", "target": "components/quill/cookie-consent.tsx" } ], "docs": "A cookie consent banner offering accept, reject, and preferences actions. Do: \"Reject non-essential\" gets the same one-click ease as \"Accept all\" — both are top-level buttons, not one button plus a buried settings link. Don't: Make rejecting cookies harder to find or reach than accepting them — that's a dark pattern, not a real choice. Use announcement-banner when the message is informational only and doesn't require an explicit accept/reject decision. Use alerts when the message is a local, in-page status, not a site-wide compliance notice." }, { "name": "dashboard", "type": "registry:block", "title": "Dashboard shell", "description": "An app shell with sidebar navigation, a search header, and KPI stat cards.", "meta": { "intent": [ "app-shell", "data-display" ], "use_when": "You need a full application home — sidebar, search header, and KPI cards." }, "registryDependencies": [ "button", "input", "badge", "card", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/dashboard.tsx", "type": "registry:component", "target": "components/quill/dashboard.tsx" } ], "docs": "An app shell with sidebar navigation, a search header, and KPI stat cards. Do: Give the current nav item a distinct active state (background + text color), not just a subtle underline. Don't: Leave every nav item looking identical — users lose track of where they are. Use sidebar-nav when you need the collapsible icon-sidebar variant with grouped menus, not this fixed-width text sidebar. Use stat-cards when you only need the KPI row, not the full shell around it." }, { "name": "data-table", "type": "registry:block", "title": "Data table", "description": "A filterable members table with a toolbar, status badges, and a per-row actions menu.", "meta": { "intent": [ "data-display" ], "use_when": "You need a filterable, actionable table of records with status badges and row actions." }, "registryDependencies": [ "table", "badge", "button", "input", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/data-table.tsx", "type": "registry:component", "target": "components/quill/data-table.tsx" } ], "docs": "A filterable members table with a toolbar, status badges, and a per-row actions menu. Do: Give every icon-only row-action button a per-row aria-label (e.g. \"Actions for Ada Lovelace\"), not a generic one. Don't: Ship a table full of icon-only buttons that all announce as the same unlabeled button to a screen reader. Use activity-feed when you want a read-only chronological list, not sortable/filterable records with row actions. Use kanban when the records are better organized by status columns than by table rows." }, { "name": "empty-state", "type": "registry:block", "title": "Empty state", "description": "A dashed-border empty state with an icon, explanatory text, and a primary action.", "meta": { "intent": [ "feedback" ], "use_when": "You need to explain why a view is empty and offer the primary action to fill it." }, "registryDependencies": [ "button", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/empty-state.tsx", "type": "registry:component", "target": "components/quill/empty-state.tsx" } ], "docs": "A dashed-border empty state with an icon, explanatory text, and a primary action. Do: Pair the explanation with a real, primary action button that actually fills the view (e.g. \"New project\"). Don't: Explain that a view is empty and leave the user with no way to act on it from that screen. Use error-404 when the view is empty because the resource doesn't exist or the route is wrong, not because the user hasn't created anything yet. Use onboarding when there's a multi-step setup to walk through, not a single action to take right now." }, { "name": "error-404", "type": "registry:block", "title": "404 page", "description": "A full-page 404 error state with go-back and home recovery actions.", "meta": { "intent": [ "feedback" ], "use_when": "You need a full-page not-found state with recovery actions." }, "registryDependencies": [ "button", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/error-404.tsx", "type": "registry:component", "target": "components/quill/error-404.tsx" } ], "docs": "A full-page 404 error state with go-back and home recovery actions. Do: Offer both a contextual escape (Go back) and an absolute one (Take me home) — never strand the user with only one recovery path. Don't: Leave a 404 page with no action at all, or only a single 'Home' link buried in a header the user has to hunt for. Use empty-state when the resource exists but is legitimately empty (no items yet), not missing or misrouted. Use alerts when the error is recoverable inline on the current page, not a full-page dead end that needs its own route." }, { "name": "faq", "type": "registry:block", "title": "FAQ section", "description": "A centered FAQ section — an accordion of common questions with a contact call-to-action footer.", "meta": { "intent": [ "content", "marketing" ], "use_when": "You need to answer common questions in an accordion with a contact fallback." }, "registryDependencies": [ "accordion", "button" ], "files": [ { "path": "registry/blocks/faq.tsx", "type": "registry:component", "target": "components/quill/faq.tsx" } ], "docs": "A centered FAQ section — an accordion of common questions with a contact call-to-action footer. Do: Open the first question by default (defaultValue=['item-0']) so the section doesn't read as empty on load. Don't: Start every item collapsed and force a first click just to prove the section has content. Use accordion when you need a bare accordion with no marketing framing (heading, contact footer) around it. Use contact-form when the user needs to ask something not covered — this pattern only offers a CTA button, not the form itself." }, { "name": "feature-section", "type": "registry:block", "title": "Feature section", "description": "A marketing section with an editorial heading and a three-column feature grid.", "meta": { "intent": [ "marketing" ], "use_when": "You need to present product features in an editorial multi-column grid." }, "registryDependencies": [ "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/feature-section.tsx", "type": "registry:component", "target": "components/quill/feature-section.tsx" } ], "docs": "A marketing section with an editorial heading and a three-column feature grid. Do: Keep the grid to three (or a multiple of three) features so it reads evenly across the row. Don't: Let an uneven feature count leave a dangling, unbalanced last row on wide screens. Use stats-band when you're presenting numbers, not descriptive feature callouts with icons and body text. Use pricing when the features are tied to specific paid tiers, not the product as a whole." }, { "name": "file-upload", "type": "registry:block", "title": "File upload", "description": "An asset-upload card with a drag-and-drop dropzone and a progress-tracked upload queue.", "meta": { "intent": [ "form" ], "use_when": "You need drag-and-drop asset upload with a progress-tracked queue." }, "registryDependencies": [ "card", "button", "progress", "separator", "https://www.quilldesignsystem.com/r/icon.json" ], "files": [ { "path": "registry/blocks/file-upload.tsx", "type": "registry:component", "target": "components/quill/file-upload.tsx" } ], "docs": "An asset-upload card with a drag-and-drop dropzone and a progress-tracked upload queue. Do: Make the dropzone a real, focusable