negocie.aiDesign System

The negocie.ai design system

1.0

This catalog is the exploration surface for the negocie.ai shadcn/ui based design system, seeded with the brand's Azul Royal Tech and Grafite Profundo tokens. Nothing is hard-coded: every visual attribute flows from a design token. Click any value below to edit it and see the whole catalog react live.

Token-first

Colors, radii, spacing and typography are CSS variables. Change a token and every component updates.

Complete catalog

Each component shows every variant, size, orientation, state and composition — not just a default.

Accessible

Built on Base UI primitives with correct roles, labels, focus management and keyboard support.

Composable

Modular building blocks meant to be combined. Composition examples are included throughout.

Design Tokens

Tokens are the single source of truth. They are defined as CSS custom properties in globals.css, mapped to Tailwind theme variables, and consumed by every component. Every token below is directly editable from the sidebar.

How tokens flow

A raw value is assigned to a semantic token, exposed to Tailwind via @theme inline, then referenced by components through utility classes such as bg-primary or text-muted-foreground.

css
/* 1. Define the value (globals.css) */
:root {
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --radius: 0.625rem;
}

/* 2. Map it to a Tailwind theme token */
@theme inline {
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --radius-lg: var(--radius);
}

/* 3. Consume it in a component */
/* <button class="bg-primary text-primary-foreground rounded-lg" /> */

Color

A restrained, semantic palette. Neutrals carry most of the interface; feedback colors (success, warning, error, information) communicate status and should never be used decoratively. Utility tokens cover borders, form inputs and focus rings. Every swatch is editable from the sidebar — its paired foreground is recalculated automatically to preserve contrast.

Typography

The type scale is a modular scale derived from a base size and a ratio — both editable from the sidebar, reshaping every text-* utility at once.

Type scale

Base size: 16pxScale ratio: 1.250
The quick brown foxtext-xs · --text-xs
The quick brown foxtext-sm · --text-sm
The quick brown foxtext-base · --text-base
The quick brown foxtext-lg · --text-lg
The quick brown foxtext-xl · --text-xl
The quick brown foxtext-2xl · --text-2xl
The quick brown foxtext-3xl · --text-3xl
The quick brown foxtext-4xl · --text-4xl

Families

font-sans

Aa Bb Cc 123

Interface and body text. Weights 400–600.

font-mono

Aa Bb Cc 123

Code, tokens and tabular values.

Spacing

Layout rhythm uses a single base spacing unit (4px by default, editable from the sidebar). Prefer gap utilities over margins for predictable, direction-aware spacing.

Base spacing unit: 0.25rem
0.5
2.0px
1
4.0px
2
8.0px
3
12.0px
4
16.0px
6
24.0px
8
32.0px
12
48.0px
16
64.0px

Radii

All corner rounding derives from a single --radius token, editable from the sidebar. The scale (sm → 2xl) is calculated from it, so adjusting one value reshapes the whole system.

Radius: 0.625rem
sm
md
lg
xl
2xl
full

Shadows

Elevation is subtle and used sparingly — mostly for overlays (popovers, dropdowns, dialogs) to separate them from the page. Both the shadow color and its strength are editable from the sidebar.

Shadow color: #000000Strength: 100%
shadow-xs
shadow-sm
shadow-md
shadow-lg
shadow-xl

Icons

Icons come from lucide-react, the icon library configured for this project. Use a consistent stroke and let components size icons automatically; only set an explicit size for standalone, decorative usage.

Sizing

Inside components (buttons, inputs, menus) icons inherit the correct size. Standalone, use size-4 (16px), size-5 (20px) or size-6 (24px).

size-4
size-5
size-6
tsx
import { TypeIcon } from 'lucide-react'

// Standalone icon
<TypeIcon className="size-5" aria-hidden />

// Inside a button — no size class needed, use data-icon
<Button>
  <PlusIcon data-icon="inline-start" />
  New item
</Button>

Button

Button

Triggers an action or event. The button is the workhorse of the interface — its variants encode intent (primary action, secondary, destructive) while sizes adapt it to different densities.

Variants

Six visual variants map to intent. Use exactly one default (primary) button per view to signal the main action.

Rendered as a native <button> with a visible focus ring. Link variant should only be used for navigation.

Sizes

From compact toolbars (xs) to prominent CTAs (lg). Icon sizes keep a square footprint.

With icons

Icons clarify meaning. Use data-icon to position and let the component size the icon automatically.

Icon-only buttons must include an accessible label via aria-label or sr-only text.

Interaction states

Hover, focus and active states are built in. Disabled removes pointer events and lowers opacity. Loading composes a Spinner with a disabled button.

default
hover
focus
disabled
loading

Loading buttons stay disabled to prevent duplicate submissions; keep the label so intent remains clear.

Recommended

  • Use a single primary (default) button per view for the main action.
  • Pair destructive actions with a confirmation for irreversible operations.
  • Keep labels short, action-oriented verbs ("Save", "Create project").

Avoid

  • Do not stack multiple primary buttons competing for attention.
  • Avoid the link variant for actions that change data — use it for navigation.
  • Never rely on color alone to convey a destructive action.

Button Group

ButtonGroup

Visually joins related buttons into a single segmented control. Supports horizontal and vertical orientation, text/label segments, and separators.

Horizontal

Related actions merged into one control with shared borders.

Vertical

Stack the group for narrow layouts or menus.

With text & separator

Combine a static label segment with actions, split by a separator.

https://

Split button

A primary action beside a menu trigger.

Recommended

  • Use a button group for a bounded set of related, mutually visible actions (view switchers, segmented filters).
  • Keep every button in the group the same variant so the shared border reads as one control.

Avoid

  • Don't mix unrelated actions in the same group — it implies they're variations of one choice.
  • Avoid more than ~4–5 segments; beyond that, prefer a Select or Toggle Group with wrapping.

Toggle & Toggle Group

Toggle

A two-state button (on/off) and a group of related toggles for single or multiple selection. Ideal for formatting controls and view options.

Toggle — variants & sizes

Standalone toggles. The pressed state uses the muted surface.

Uses aria-pressed. Icon-only toggles need an aria-label.

States

Off, on and disabled.

off
on
disabled

Toggle Group — single

Mutually exclusive selection, like a text alignment control.

Toggle Group — joined & multiple

spacing={0} joins items; multiple allows more than one active value.

Recommended

  • Use a standalone Toggle for a single independent on/off setting (e.g. mute, favorite).
  • Use Toggle Group when several toggles represent one decision — single selection for exclusive choices, multiple for independent flags.

Avoid

  • Don't use Toggle where a Checkbox or Switch is the expected control — reserve it for icon-driven, button-like affordances.
  • Avoid unlabeled icon-only toggles without an aria-label; the pressed state alone is not enough for screen reader users.

Badge

Badge

A compact label for status, counts and metadata. Non-interactive by default, but can render as a link. Use variants to communicate meaning.

Variants

Match the badge variant to the surrounding intent.

DefaultSecondaryOutlineDestructiveGhost

With icons & counts

Add a leading icon for status or use as a numeric counter.

Featured81.0.0

Status dots

Semantic status

Combine a colored dot with an outline badge to convey status using feedback tokens.

ActivePendingFailedDraft

Recommended

  • Pair a status dot with a text label — never ship color as the only signal.
  • Keep badge text to one or two words; move detail to a tooltip or the surrounding content.

Avoid

  • Don't make a badge interactive on its own — wrap it in a button/link if it needs to be clickable.
  • Avoid using more than one badge variant for the same meaning across the app (pick one mapping and stay consistent).

Input & Field

input · field

Text entry controls and the Field primitives that wire labels, descriptions, and validation together. Inputs share --input, --ring, and --border tokens for a consistent focus treatment.

States

Default, disabled, and invalid share one token set.

Input group

Compose icons, text, and units inside an input with InputGroupAddon.

$
USD

Field: label, description, error

data-invalid on Field pairs with aria-invalid on the control for accessible validation.

We'll never share your email.

FieldLabel is associated via htmlFor; FieldError is announced to assistive tech when the field is invalid.

Field set with legend

Group related controls with FieldSet and FieldLegend for accessible structure.

Notifications

Choose how you want to be reached.

Recommended

  • Wrap every control in a Field with an associated FieldLabel.
  • Use FieldError with data-invalid + aria-invalid for validation.
  • Group related fields with FieldSet and FieldLegend.

Avoid

  • Don't lay out forms with raw divs and space-y utilities.
  • Don't put a raw Input inside an InputGroup — use InputGroupInput.
  • Don't rely on placeholder text as the only label.

Textarea

textarea

Multi-line text entry that inherits the same border, ring, and invalid tokens as Input.

Default

States

Disabled and invalid states.

Recommended

  • Set rows to size the control for the expected content (comments vs. long-form notes).
  • Pair with FieldDescription to hint at length limits or formatting.
  • Use aria-invalid + FieldError together, never color alone, to signal validation.

Avoid

  • Don't use Textarea for single-line values like names or emails — use Input.
  • Don't disable resize without a good reason; users rely on it for long text.
  • Don't rely on placeholder text as a substitute for a FieldLabel.

Checkbox

checkbox

Selection control for independent boolean options. Checked, unchecked, and disabled states are all token-driven.

States

Interaction states

Focus-visible uses the shared --ring token; disabled removes pointer events and lowers opacity.

unchecked
checked
focus
disabled

The clickable hit area extends beyond the visible box (after:-inset-x-3 after:-inset-y-2) to meet the 24px minimum target size.

Recommended

  • Always pair a Checkbox with a Label (or aria-label) — never ship an unlabeled box.
  • Use Checkbox for independent, multi-select choices; use RadioGroup for mutually exclusive ones.
  • Reflect indeterminate state explicitly when a parent checkbox covers partial child selections.

Avoid

  • Don't use Checkbox to trigger an immediate action — that's what Switch or Button are for.
  • Don't shrink the hit area with custom sizing; the extended after: target keeps it tappable.
  • Don't rely on color alone for the checked state — the check glyph must remain visible.

Radio Group

radio-group

Mutually exclusive choice among a small set of options. Items always live inside RadioGroup.

Vertical

Horizontal

Stacks vertically on narrow viewports and lays out inline once there's room, keeping labels legible at every width.

Below sm the options wrap to one per row; from sm upward they sit on a single row.

Recommended

  • Always set a defaultValue or controlled value so one option is selected by default when appropriate.
  • Keep option labels short enough to stay legible in a horizontal layout on small screens.
  • Use RadioGroup for 2-6 mutually exclusive options; switch to Select once the list grows long.

Avoid

  • Don't mix RadioGroupItem with plain <input type="radio"> — state and styling won't sync.
  • Don't omit a visible Label for each item; relying on adjacent text alone breaks click targets.
  • Don't use a radio group for a single yes/no choice — a Switch or Checkbox is clearer.

Switch

switch

Toggle for instant on/off settings. The thumb and track colors come from primary and input tokens.

States

Interaction states

Focus-visible draws the same ring token as other controls; the thumb slides via a transform, not a layout shift.

off
on
focus
disabled

Switch exposes role=switch with aria-checked; announce the setting name via an adjacent Label, not the state alone.

Recommended

  • Use Switch for settings that take effect immediately, without a separate save action.
  • Label the setting itself ("Push notifications"), not the state ("On"/"Off").
  • Use the sm size only in dense rows (e.g. tables) — default size everywhere else.

Avoid

  • Don't use Switch inside a form that requires an explicit Submit — use Checkbox instead.
  • Don't pair a Switch with a label that describes the current value; it becomes stale once toggled.
  • Don't use two Switches to express a mutually exclusive choice — that's a RadioGroup.

Select

select

Dropdown selection built from SelectItem inside SelectGroup. The popup surface uses popover tokens.

Default

Long content

The trigger truncates its value; the popup scrolls once the option list exceeds the viewport.

The trigger keeps a fixed max-width and wraps to full width on narrow screens; the content panel repositions to stay within the viewport.

Disabled

Recommended

  • Use Select when options are known and fixed; switch to Combobox once users need to search or filter.
  • Group related options with SelectGroup so screen readers announce structure.
  • Provide a placeholder via SelectValue so the empty state is never a blank trigger.

Avoid

  • Don't use Select for fewer than ~5 options — RadioGroup keeps every choice visible at once.
  • Don't put more than a couple dozen items in a single Select without search — use Combobox instead.
  • Don't truncate the trigger value without an accessible full label available (e.g. via title or SelectValue text).

Slider

slider

Range selection bound to component state. The track and range use muted and primary tokens.

Controlled

Volume60

Range

Two thumbs produce a min/max range.

Disabled

Recommended

  • Always show the current numeric value near the track (label, tooltip, or adjacent text).
  • Pick a step that matches meaningful increments (1 for percentages, 5-10 for coarser ranges).
  • Use the two-thumb variant only for genuine min/max ranges, not two unrelated values.

Avoid

  • Don't use Slider for precise numeric entry — pair it with or replace it by an Input[type=number].
  • Don't omit min/max/step; relying on defaults can produce a mismatched scale for the data.
  • Don't use Slider on touch targets smaller than the thumb's hit area — keep the track full width.

Input OTP

input-otp

One-time-password entry built on the input-otp library. Each slot reflects a single character and shares the same border and ring tokens as Input.

6-digit code

A single group of six slots for a verification code.

Renders as a single native input under the hood, so autofill, paste, and SMS one-time-code suggestions work as expected; each slot shows a fake caret (hasFakeCaret) only while active.

Grouped with separator

An invite code split 4+4 with InputOTPSeparator marking the boundary.

Disabled

1
2
3

Recommended

  • Set maxLength to match the exact code length your backend issues.
  • Group slots with InputOTPSeparator when the issued format has a visual break (e.g. XXXX-XXXX).
  • Trigger validation once maxLength is reached via onComplete rather than on every keystroke.

Avoid

  • Don't use InputOTP for free-form text or passwords — it's purpose-built for fixed-length codes.
  • Don't disable paste; pasting a full code from SMS or a password manager is the primary use case.
  • Don't add a visible label per slot — describe the whole field once with a Field/FieldLabel wrapper.

Calendar

calendar

Date picker surface built on react-day-picker. Selected, range, and disabled days are token-driven and compose into DatePicker/DateRangePicker.

Single date

Controlled single-date selection.

August 2026

Arrow keys move focus between days, PageUp/PageDown change month, and Home/End jump to the start/end of the week — all native to react-day-picker.

Range, two months

numberOfMonths={2} shows the current and next month side by side for range selection.

August 2026
September 2026

Two months sit side by side from md upward; below md they stack vertically so neither month gets clipped.

Disabled past dates

disabled={{ before: new Date() }} blocks selection of any day before today.

August 2026

Disabled days are still visible for context but are unreachable via keyboard focus and announced as unavailable.

Chevron icons mirror automatically in RTL layouts via rtl:**:[.rdp-button_next>svg]:rotate-180.

Recommended

  • Use the disabled matcher (e.g. { before: new Date() }) to block out-of-range dates instead of validating after the fact.
  • Prefer the DatePicker/DateRangePicker composition when the calendar should live in a popover, not inline.
  • Set numberOfMonths={2} for range selection so both endpoints can be picked without navigating.

Avoid

  • Don't render Calendar inline in dense forms — it takes real estate; use DatePicker's popover instead.
  • Don't forget locale when the app supports non-English formats; pass locale to align month/weekday names.
  • Don't mutate the selected Date/DateRange in place — always create a new value in onSelect.

Combobox

combobox

Popover + Command composition for searchable single selection. Unlike Select, options can be filtered by typing, via cmdk.

Searchable list

Type to filter frameworks by label.

The trigger exposes role="combobox" with aria-expanded; the filtered list is a Command list with roving focus.

With a disabled option

Individual options can be marked disabled and stay unselectable while remaining visible.

Disabled

Recommended

  • Reach for Combobox once the option list is long enough that scanning beats scrolling — search filters it instantly.
  • Keep emptyText actionable (e.g. "No framework found.") instead of a generic message.
  • Use disabled on individual options for choices that exist but are temporarily unavailable.

Avoid

  • Don't use Combobox for short, fixed lists (under ~8 items) — a plain Select is simpler and needs no typing.
  • Don't forget a clear placeholder; an empty trigger with no context reads as broken.
  • Don't rely on the visual checkmark alone — CommandItem already carries the selected state for assistive tech.

Date Picker

date-picker · date-range-picker

Popover + Calendar + date-fns composition for picking a single date or a range from a compact trigger button.

Single date

Formats the selected date with date-fns (PPP) once chosen.

Date range

Shows a formatted 'from – to' label and opens a two-month calendar.

The popover content sizes to the calendar's natural width and repositions to stay inside the viewport on narrow screens.

Disabled

Both pickers inherit Calendar's native keyboard navigation once opened; the trigger itself is a Button, so it's focusable and operable with Enter/Space.

Recommended

  • Use DatePicker/DateRangePicker for compact forms; drop to a bare Calendar when space allows an always-visible picker.
  • Show the formatted value in the trigger itself so the current selection never requires opening the popover to confirm.
  • Constrain selectable dates via the underlying Calendar's disabled matcher for booking-style flows.

Avoid

  • Don't parse or format dates manually in consuming code — the components already use date-fns internally.
  • Don't nest a DatePicker inside another popover/dialog without checking focus return on close.
  • Don't use DateRangePicker when only a single date is ever needed — the wider trigger and two-month calendar add noise.

Form

react-hook-form + zod

This shadcn style (base-nova) has no dedicated Form wrapper component — there is no components/ui/form.tsx in the registry. Instead, forms are composed directly from the Field primitives (Field, FieldGroup, FieldLabel, FieldDescription, FieldError) wired to react-hook-form's useForm and validated declaratively with a zod schema via @hookform/resolvers/zod.

Profile form

A complete, functional form: react-hook-form manages field state and submission, zodResolver validates against the schema below, and each Field reports its own error through FieldError bound to form.formState.errors.

This is your public display name.

We'll only use this to contact you.

Optional. Max 160 characters.

FieldError uses role='alert' and is associated with the control via aria-invalid/aria-describedby when present; react-hook-form re-validates on blur/change according to the configured mode.

FieldGroup stacks fields vertically by default; use Field orientation='responsive' for fields that sit side by side and collapse on mobile.

Single field with a visible error

For the raw, uncontrolled Field/FieldLabel/FieldDescription/FieldError composition pattern with an error already shown (no interaction required), see the 'Field: label, description, error' example in the Input & Field section above — the same data-invalid + aria-invalid + FieldError wiring applies here, just driven by form.formState.errors instead of a static prop.

Recommended

  • Use zodResolver to validate declaratively against a single schema instead of hand-rolled validation logic.
  • Bind FieldError to the errors from form.formState so messages stay in sync with react-hook-form state.
  • Disable the submit control while form.formState.isSubmitting is true when submission is asynchronous.

Avoid

  • Don't validate only on submit without inline feedback — surface errors as the user fills the form.
  • Don't use generic error messages like "Invalid" — be specific, as in the schema above ("Username must be at least 2 characters.").
  • Don't mix native HTML5 validation (required, pattern, minLength) with zod on the same field — pick one source of truth.

Avatar

Avatar

Represents a user or entity. Falls back to initials (or an icon) when no image is available, and can be grouped or badged with a status indicator.

Sizes

Three sizes for different densities — tables, lists, and profile headers.

SMMDLG

Image & fallback

AvatarImage renders when it loads; AvatarFallback covers loading/error states and entities with no photo.

JC

Provide meaningful alt text on AvatarImage; decorative icon fallbacks should be aria-hidden.

With status badge

AvatarBadge overlays a status dot or icon; scales with the avatar size.

ON
online
AW
away
OF
offline

A status conveyed only by color needs a text equivalent nearby (e.g. in a tooltip or sr-only span).

Avatar group

Overlapping stack for a compact list of participants, with an overflow count.

ABCDEF
+4

Recommended

  • Always provide a fallback (initials or icon) for failed/missing images.
  • Keep group counts accurate and clickable when they open a full list.

Avoid

  • Do not rely on a status badge color as the only signal of state.
  • Avoid mixing avatar sizes within the same group.

Card

Card

A general-purpose container for grouping related content and actions. Composes header, content, footer and an optional action slot.

Basic

Header, content and footer — the most common composition.

Team plan
For growing teams that need more room.

Up to 20 members, unlimited projects.

With action

CardAction anchors a control (menu, toggle) to the top-right of the header grid.

Invoice #1042
Due Aug 1, 2026

$1,240.00

Compact (size='sm')

Tighter padding for dense dashboards and sidebars.

Storage

18.2 GB of 50 GB used

Interactive / clickable

A card can act as a navigable surface. Keep focus and hover feedback visible.

View analytics
See traffic and conversion for the last 30 days.

If the whole card is clickable, wrap it in a single interactive element (link or button) rather than nesting interactive children.

Recommended

  • Use CardHeader/CardContent/CardFooter to keep spacing consistent across the catalog.
  • Reserve CardAction for a single, secondary control (menu, toggle) — not a primary CTA.

Avoid

  • Do not nest another interactive card or link inside a fully clickable card.
  • Avoid stuffing unrelated content into one card — split into multiple cards instead.

Table

Table

Displays tabular data with header, body, optional footer and caption. A semantic HTML table under the hood, horizontally scrollable on narrow screens.

Standard table

Header row, body rows and a footer summarizing a column.

Recent invoices.
InvoiceStatusMethodAmount
INV001PaidCredit card$250.00
INV002PendingPayPal$150.00
INV003FailedBank transfer$350.00
Total$750.00

Use <TableHead> (th) for column headers so assistive tech announces them when navigating cells.

The table container scrolls horizontally rather than truncating columns on narrow viewports.

Sortable header & row states

Clickable column headers signal sort order; hover and selected states help scanning.

Name
Default row$100
Selected row$200

Recommended

  • Right-align numeric columns for scannability.
  • Pair status columns with color + text/badge, not color alone.

Avoid

  • Do not use a table for single-record layouts — use a description list instead.
  • Avoid more than ~6 columns on typical viewports; consider a card layout on mobile.

Accordion

Accordion

Vertically stacked, collapsible sections. Supports single or multiple open panels at once, and can start with an item expanded.

Single (default)

Only one panel open at a time — good for FAQs and settings groups.

Yes. It follows the WAI-ARIA accordion pattern with correct roles and keyboard support.

Trigger is a real button with aria-expanded; ChevronDown/Up swap to reflect state.

Multiple open

The multiple prop allows several sections to be expanded simultaneously — useful for long-form reference content.

Ships within 2–3 business days.

Free returns within 30 days of delivery.

Recommended

  • Use single-open mode for FAQs and settings groups where panels are mutually exclusive.
  • Keep triggers as short, scannable phrases so users can find the right section at a glance.

Avoid

  • Do not hide content a user needs immediately (e.g. a required form field) inside a collapsed panel.
  • Avoid nesting accordions within accordions — flatten the hierarchy or use tabs instead.

Tabs

Tabs

Organizes content into switchable panels sharing the same space. Two visual styles (default, line), two orientations, and full keyboard support.

Default style

Segmented, pill-style tab list — the most common pattern for dashboards and forms.

Update your account details.

Uses the WAI-ARIA tabs pattern: arrow keys move focus, Tab enters/exits the tablist.

Line style

An underline indicator instead of a filled pill — reads as more editorial, less chrome-heavy.

High-level summary.

Vertical orientation

Useful for settings screens where the list of sections is long relative to panel content.

General settings.

RTL support

The whole catalog can flip direction from the sidebar control — tabs reorder and the active indicator follows the writing direction automatically.

محتوى علامة التبويب الأولى.

Recommended

  • Use tabs for peer content at the same level — not for a linear, multi-step flow.
  • Keep tab labels to one or two words; use icons only alongside text, not instead of it, unless universally understood.

Avoid

  • Do not put a primary call-to-action inside a tab a user might not open.
  • Avoid more than 5–6 horizontal tabs; switch to vertical or a select on narrow screens.

Progress

Progress

Communicates completion of a task with a determinate value, or an indeterminate state while duration is unknown.

Determinate values

A known percentage of completion.

x
x
x

Renders with role='progressbar' and aria-valuenow/min/max derived from the value prop.

With label & value

ProgressLabel and ProgressValue compose a header row above the track.

Uploading
x

Indeterminate

Omit value while the duration is unknown; pair with a spinner or skeleton for the surrounding content.

x

Recommended

  • Show a determinate value whenever the duration or completion ratio is knowable.
  • Pair long-running progress with a text label describing what is happening (e.g. "Uploading").

Avoid

  • Do not use progress bars for instantaneous actions — a spinner or disabled button state is enough.
  • Avoid updating the value so rapidly that it becomes visually noisy; throttle updates instead.

Data Table

DataTable

A batteries-included table built on TanStack Table: sorting, per-column filtering, column visibility, row selection and pagination on top of the semantic Table primitive.

Payments

Sortable Amount column, a filter on Email, a column-visibility dropdown and pagination footer.

StatusEmail
successken99@example.com
$316.00
successabe45@example.com
$242.00
processingmonserrat44@example.com
$837.00
successsilas22@example.com
$874.00
failedcarmella@example.com
$721.00
pendingbrandon91@example.com
$129.00
successdiana77@example.com
$458.00
processingjordan.lee@example.com
$592.00

0 of 8 row(s) selected.

Renders the same semantic table markup as the Table component (thead/th), so it inherits native table accessibility; the header sort control is a real button.

The toolbar (filter input + columns dropdown) wraps on narrow screens, and the table body scrolls horizontally rather than truncating columns.

Recommended

  • Give at least one column a stable, human-readable filter (e.g. name or email) so the toolbar is useful.
  • Reserve column-visibility toggles for tables with more columns than fit comfortably on screen.

Avoid

  • Do not enable row selection without giving the checkbox column an accessible label.
  • Avoid client-side sorting/filtering on very large datasets — paginate from the server instead.

Separator

Separator

A thin visual divider between groups of content. Orientation determines whether it spans width (horizontal) or height (vertical), and it is purely decorative unless given semantic meaning.

Horizontal

Divides stacked blocks of content, such as sections of a settings list.

Design System

An open-source component catalog.

BlogDocsSource

Decorative by default (role='none' via aria-orientation handling) — do not rely on it to convey structure to screen reader users; use headings instead.

Vertical in a toolbar

Groups related actions inside a horizontal flex container without a visible border.

Recommended

  • Use a separator to group related content within the same visual region.
  • Set orientation="vertical" only inside a flex/inline-flex container with a defined height.
  • Prefer spacing (margin/gap) over separators when the grouping is already visually obvious.

Avoid

  • Do not use a separator as a substitute for a semantic heading or landmark.
  • Avoid stacking multiple separators in a row — simplify the layout instead.

Aspect Ratio

AspectRatio

Constrains a child (typically an image or embed) to a fixed width/height ratio, so media does not shift layout while loading.

16 / 9 (widescreen)

The default for hero images, video thumbnails and link previews.

16:9

Provide a meaningful alt on the media element itself — the ratio wrapper is purely presentational.

1 / 1 (square)

Common for avatars, thumbnails and product grid tiles.

1:1

4 / 3

A slightly taller ratio, useful for classic photo grids and document previews.

4:3

Ratio is preserved at every viewport width — only the rendered pixel size changes, so media never jumps as it loads.

Recommended

  • Reserve space for media with AspectRatio to prevent cumulative layout shift while it loads.
  • Combine with object-cover on the inner image so it fills the box without distortion.

Avoid

  • Do not nest interactive controls that need their own aspect ratio inside the same wrapper.
  • Avoid arbitrary one-off ratios — stick to a small shared set (16/9, 1/1, 4/3) for visual consistency.

Scroll Area

ScrollArea

A styled, cross-browser-consistent scrollable viewport with a custom scrollbar and thumb, for both vertical and horizontal overflow.

Vertical list

Caps the height of a long list of items while keeping the surrounding layout fixed.

The viewport is keyboard-focusable and scrollable with arrow keys / Page Up-Down once focused, with a visible focus ring.

Horizontal gallery

Set the inner content to w-max and flex-row so it overflows horizontally; the scrollbar orientation follows the overflow direction.

On touch devices the gallery scrolls natively via swipe; the visible scrollbar is a desktop affordance layered on top.

Recommended

  • Constrain height (vertical) or width (horizontal) with an explicit class so the scroll boundary is predictable.
  • Use for self-contained regions (sidebars, previews, chat panes) rather than the whole page.

Avoid

  • Do not nest a ScrollArea inside another scrollable ancestor without a clear visual boundary — nested scrolling confuses users.
  • Avoid hiding critical actions below the fold of a scroll area with no visual cue that content continues.

Collapsible

Collapsible

Toggles the visibility of a single content panel. Simpler than Accordion — no item list, just an open/closed boolean, ideal for 'show more' patterns.

Default

A trigger button expands and collapses the panel below it.

3 team members

@alefe
@camila
@rafael

CollapsibleTrigger renders as a real button with aria-expanded; the chevron rotates to reflect state without relying on the icon alone.

Closed by default — 'show more'

A list that reveals additional detail on demand, keeping the collapsed state compact for scanning.

Order #1042 — Delivered

Recommended

  • Use for a single, independent show/hide panel — reach for Accordion when there are several mutually related items.
  • Keep the trigger label or icon consistent with the current state ("Show details" vs "Hide details").

Avoid

  • Do not hide content required to complete a primary task behind a collapsible.
  • Avoid animating so slowly that repeated toggling feels sluggish.

Kbd

Kbd

Renders a keyboard key or shortcut combination as inline UI, typically next to a command, menu item or tooltip.

Single keys

Isolated keys for referencing a single input.

EscEnter

Kbd is visual only (pointer-events-none); it does not announce as a shortcut to screen readers — pair it with a visible or sr-only text label describing the action.

Combinations with KbdGroup

Group multiple keys to represent a chord, such as a save or search shortcut.

CtrlKP

Inside a button

Surfaces the shortcut for an action right where the action lives, such as a command palette trigger.

Hide the shortcut hint below the sm breakpoint (className='hidden sm:inline-flex') since most touch devices have no physical keyboard.

Inside a tooltip

Kbd automatically adapts its background/foreground when nested in a tooltip, staying legible against the inverted surface.

Recommended

  • Use Kbd to document real, working keyboard shortcuts — keep it in sync with the actual key handler.
  • Group multi-key chords with KbdGroup rather than a single Kbd containing "Ctrl+K".

Avoid

  • Do not use Kbd as a generic small-badge component for unrelated labels — it implies a literal key.
  • Avoid showing shortcuts that only work on desktop as the sole affordance on touch layouts.

Item

Item

A generic, composable row primitive for lists of files, contacts, notifications or settings — media on the left, title/description in the middle, actions on the right.

File list

ItemGroup stacks Items separated by ItemSeparator; ItemMedia holds an icon, ItemContent the text, ItemActions the trailing controls.

Q3-report.pdf

2.4 MB · Updated 2 days ago

hero-banner.png

860 KB · Updated 5 days ago

notes.txt

4 KB · Updated just now

ItemGroup renders role='list' so assistive tech announces the number of entries; keep each Item's action reachable by keyboard (a real button, not a div with onClick).

ItemContent truncates title to one line and description to two via line-clamp, so rows stay a predictable height as the list narrows on mobile.

Visual variants

default is borderless, outline adds a visible border, muted fills the row with a subtle surface — pick one per list, not mixed within it.

Default
default
Outline
outline
Muted
muted

Sizes

default/sm share spacing; xs tightens gaps and drops the description's line-height for very dense lists, like a command menu.

Default size

Standard row height

Small size

Compact row height

Extra small size

RTL support

ItemActions stays on the trailing edge in either direction — in RTL that reads as the visual left, since 'trailing' follows text direction rather than a fixed side.

رسالة جديدة

من فريق الدعم · منذ 3 ساعات

Recommended

  • Keep one variant per list for visual consistency; switch variant only to distinguish a different list context.
  • Put the single most relevant action in ItemActions and move the rest into a menu behind MoreHorizontal.
  • Use ItemMedia variant="image" for entities with a photo (contacts, products) and variant="icon" for types/categories.

Avoid

  • Do not put more than one or two primary actions directly in ItemActions — it competes for attention and breaks on narrow screens.
  • Avoid using Item for a single, one-off row — it is built for repeated, list-shaped content.

Resizable

Resizable

A panel group with draggable handles for user-adjustable layouts — sidebars, split editors, or preview panes. Built on react-resizable-panels.

Horizontal

Two side-by-side panels with a shared draggable handle; defaultSize is a percentage of the group.

Sidebar
Content

ResizableHandle is keyboard-operable: focus it and use the arrow keys to resize in fixed increments.

Vertical

orientation='vertical' stacks panels top-to-bottom and the handle drags along the y axis.

Header
Body

Nested groups — editor layout

A group inside a panel composes a three-pane layout: a fixed sidebar, and a vertical split between editor and preview.

Sidebar
Editor
Preview

Below a comfortable width, prefer collapsing this into stacked, non-resizable sections (or tabs) rather than shrinking three live panels — dragging thin handles is impractical on touch.

RTL support

In a right-to-left context the first panel renders on the visual right; the handle drag direction inverts to match, so 'grow the first panel' still means dragging toward the trailing edge.

اللوحة الأولى
اللوحة الثانية

Recommended

  • Set a sensible minSize on every panel so a drag cannot collapse critical UI to zero width.
  • Persist user-adjusted sizes (via onLayout) for layouts users return to often, like a code editor.
  • Always render withHandle for a visible grab affordance — an invisible hairline handle is hard to discover.

Avoid

  • Do not use Resizable for layouts that must remain pixel-perfect (e.g. a fixed-width brand rail).
  • Avoid deeply nested panel groups beyond two levels — the interaction becomes hard to predict.
  • Do not rely on resizable panels as the only way to reach content on small/touch screens — provide a stacked fallback.

Pagination

Pagination

Navigates between pages of a paged list or table. Built from plain links/buttons so it can be wired to client-side or server-side routing.

Standard

Previous/Next plus numbered links; the active page uses the outline variant.

The current page link carries aria-current='page'; Previous/Next expose descriptive aria-labels.

Compact (mobile)

Only Previous/Next, with the label hidden below the sm breakpoint — the built-in responsive behaviour of PaginationPrevious/Next.

Text labels ('Previous', 'Next') hide under the sm breakpoint automatically, leaving icon-only controls on small screens.

Disabled edges

On the first or last page, disable the corresponding control rather than hiding it.

Recommended

  • Show enough page numbers to orient the user, collapsing the middle with an ellipsis for long ranges.
  • Disable, rather than remove, Previous/Next at the boundaries.

Avoid

  • Do not use pagination for infinite or continuously loading feeds — prefer a "Load more" pattern or infinite scroll.

Dialog

Dialog

A modal window that interrupts the flow to request a decision or focused input. Traps focus, dims the page, and closes on escape, backdrop click, or an explicit action.

Standard

Header, description and a footer with cancel/confirm actions.

Focus is trapped inside the popup while open and returns to the trigger on close; the title is announced via DialogTitle.

Destructive confirmation

Irreversible actions should always require an explicit confirmation step.

Without close button

showCloseButton={false} forces an explicit choice via footer actions — use sparingly, for critical decisions only.

Recommended

  • Reserve dialogs for focused decisions or short forms that block the current flow.
  • Give every dialog a clear title and, for destructive actions, an unambiguous confirm label ("Delete account", not just "OK").

Avoid

  • Do not stack dialogs on top of dialogs — use a single modal at a time.
  • Avoid long forms or scrollable content in a dialog; use a full page or a Sheet instead.

Tooltip

Tooltip

A small floating label that appears on hover or keyboard focus to describe an element — typically an icon-only control. Never contains interactive content.

Sides

Position relative to the trigger: top, right, bottom, left.

Appears on both hover and keyboard focus; disappears on Escape. Do not put a tooltip's only content behind hover, since it must also work for keyboard and touch users — pair icon-only buttons with aria-label too.

With keyboard shortcut

Combine a short description with a Kbd-style shortcut hint.

Recommended

  • Use tooltips to label icon-only controls or clarify truncated text.
  • Keep content to a short phrase — a few words, not a paragraph.

Avoid

  • Do not put essential information only in a tooltip; touch devices have no hover.
  • Never place interactive elements (buttons, links) inside a tooltip — use a Popover instead.

Alert Dialog

AlertDialog

An interruptive modal reserved for critical decisions. Unlike Dialog, it has no close button and does not dismiss on backdrop click or escape by accident — the user must explicitly cancel or confirm.

Destructive confirmation

The default pattern for an irreversible action: title, description, cancel and a destructive confirm.

Focus moves to the popup on open and is trapped there; AlertDialogTitle and AlertDialogDescription are announced together so screen reader users hear the full consequence before choosing.

Compact size

size="sm" renders a narrower popup with the footer actions laid out as a 2-column grid.

With media icon

AlertDialogMedia adds a decorative icon to reinforce the severity of the decision.

Recommended

  • Reserve AlertDialog for decisions with real consequences (delete, discard, sign out) where an accidental dismiss would be worse than an extra click.
  • Label the confirm action with the verb being performed ("Delete", "Discard") rather than a generic "OK".
  • Use size="sm" for binary yes/no choices to keep the footer buttons visually balanced.

Avoid

  • Do not use AlertDialog for routine forms or non-destructive flows — it removes the escape hatches users expect from Dialog.
  • Do not skip AlertDialogCancel; always give an obvious way out that does not perform the action.

Popover

Popover

A floating panel anchored to a trigger, opened by click rather than hover. Unlike Tooltip or HoverCard, it can hold interactive content such as forms and buttons.

Standard

Header, title, description and a single action.

Opens on click or Enter/Space on the trigger; Escape closes it and returns focus to the trigger.

With a form

Interactive controls (inputs, buttons) are safe inside a Popover — this is the key difference from Tooltip/HoverCard.

Sides

side="top" | "right" | "bottom" | "left" controls where the popup opens relative to the trigger.

Recommended

  • Use Popover when the floating content needs to be clicked, typed into, or otherwise interacted with.
  • Keep the panel focused on one task — a short form or a single decision, not a full page.

Avoid

  • Do not use Popover just to show a label or description with no interaction — use Tooltip or HoverCard instead.
  • Avoid nesting a Popover trigger inside another overlay unless the interaction really requires it; prefer a single layer of floating UI.

Hover Card

HoverCard

A rich preview panel that opens on hover or keyboard focus, without navigating away. Typically used to preview a user, link or entity referenced inline.

User preview

Hovering (or focusing via keyboard) a mention reveals a preview card with avatar, name and bio.

Reported by @shadcn two hours ago.

Also opens on keyboard focus so it is reachable without a mouse, but — like Tooltip — it never works on touch. Never make it the only way to reach information a user needs.

Recommended

  • Use HoverCard to give extra context about an inline reference (user, link, term) without a navigation.
  • Keep the preview lightweight — avatar, name, one or two lines; it should not require scrolling.

Avoid

  • Do not rely on HoverCard to expose information or actions unavailable elsewhere — touch devices cannot hover.
  • Do not put form controls or destructive actions inside a HoverCard — use Popover for anything interactive.

Drawer

Drawer

A panel that slides in and can be swiped away, anchored to an edge of the viewport. The mobile-first counterpart to Dialog — well suited to touch gestures.

Bottom drawer

The default swipeDirection="down" slides up from the bottom, with a swipe handle for touch users.

Focus is trapped in the drawer while open; the swipe handle is decorative (aria-hidden) — keyboard and screen reader users close it via DrawerClose or Escape.

Prefer Drawer over Dialog on small/touch screens: it anchors to an edge, supports swipe-to-dismiss, and avoids the disorientation of a centered modal on a small viewport.

Side drawer

swipeDirection="right" (or "left") turns the drawer into a horizontal panel, useful for secondary navigation on touch devices.

Recommended

  • Use Drawer for actions and short forms on mobile/touch layouts where a swipe gesture feels native.
  • Pair showSwipeHandle with a bottom drawer so touch users have a clear affordance to drag.

Avoid

  • Do not use Drawer for large, complex forms — break the task down or use a full page instead.
  • Avoid mixing swipe directions inconsistently across the same product; pick one per surface (e.g. always bottom on mobile).

Sheet

Sheet

A panel that slides in from an edge of the screen and stays there, without the swipe-to-dismiss gesture of Drawer. Ideal for navigation, filters and side forms on desktop.

Sides

side="top" | "right" | "bottom" | "left" — right is the default.

Focus is trapped while open and returns to the trigger on close; the close button carries a visually-hidden label for screen readers.

Filters form

A common pattern: a lateral form that narrows a list without leaving the page.

On narrow viewports a right/left Sheet still works well since it caps its width at max-w-sm and becomes effectively full-width below that breakpoint.

Recommended

  • Use Sheet for filters, secondary navigation, or forms that benefit from staying anchored to an edge alongside the page content.
  • Choose the side that matches the source of the trigger (e.g. a "Filters" button on the right opens a right Sheet).

Avoid

  • Do not use Sheet for critical, must-answer decisions — it can be dismissed via backdrop click like Dialog; use AlertDialog for those.
  • Avoid stacking a Sheet on top of another Sheet or Dialog; keep a single overlay layer active at a time.

Context Menu

ContextMenu

A menu triggered by right-click (or long-press on touch), anchored to the pointer position. Supports the same groups, checkboxes, radio items and submenus as Dropdown Menu.

Rich context menu

Right-click the area below to open a menu with a group, checkbox, radio group, submenu and a destructive item.

Right-click here

Opens via right-click, the Menu/Shift+F10 key, or long-press on touch; once open it supports the same arrow-key navigation as a standard menu.

Recommended

  • Use ContextMenu to offer shortcuts for actions already available elsewhere in the UI (it should never be the only way to perform an action).
  • Demarcate the trigger area visually (border, background) so users know right-click does something there.

Avoid

  • Do not rely on ContextMenu on touch-only surfaces without also exposing the actions via a visible control — long-press discovery is poor.
  • Avoid overriding the browser context menu on areas with selectable text or links unless the custom menu is clearly more useful.

Command

Command / CommandDialog

A searchable list of commands, filtered as the user types. Use Command inline for an embedded command list, or CommandDialog to open it as a full command palette.

Inline command list

Command rendered directly on the page — search input, grouped items, shortcuts and a separator.

Arrow keys move the highlighted item, Enter selects it; CommandEmpty is announced when no results match the query.

Command palette

CommandDialog wraps Command in a Dialog controlled via open/onOpenChange — the classic ⌘K palette pattern.

Command Palette

Search for a command to run...

Bound here to ⌘K / Ctrl+K as well as the button; the dialog title and description are visually hidden but still announced to screen readers. Escape closes the palette.

Recommended

  • Use CommandDialog for a global, keyboard-driven entry point (⌘K) to navigation and actions across the app.
  • Group related items with CommandGroup and show shortcuts with CommandShortcut so power users can act without the mouse.
  • Always provide CommandEmpty feedback so a no-match search state is not silent.

Avoid

  • Do not bury actions only inside the command palette — it should accelerate existing UI, not replace visible affordances.
  • Avoid overly long, unstructured lists; group and label items so filtering stays meaningful as the list grows.

Alert

Alert

An inline, persistent message calling attention to information the user should notice while scanning a page. Unlike a toast, it does not disappear on its own.

Default & destructive

The component ships two built-in variants; feedback colors extend it via className.

Rendered with role='alert' so assistive tech announces it when it enters the DOM.

Semantic feedback colors

Success, warning and info states use the corresponding tokens directly on top of the base component.

With action

AlertAction anchors a control (button, close icon) to the top-right corner.

Recommended

  • Reserve destructive/warning styling for genuine problems that need attention.
  • Keep the description short and actionable; link to more detail rather than pasting it in.

Avoid

  • Do not use an Alert for transient confirmations — use a Toast instead.
  • Avoid stacking more than 2–3 alerts on a single view.

Toast (Sonner)

Sonner

A transient, non-blocking notification for confirming an action or reporting a background event. Stacks, auto-dismisses, and never traps focus.

Variants

Click to fire each toast type. All are announced politely without stealing focus from the page.

Rendered in a live region so screen readers announce new toasts without interrupting the current task.

With description & action

A title, supporting detail and an inline action button.

Loading / promise

Shows a spinner state, then resolves to success or error based on the promise outcome.

Recommended

  • Use toasts for confirmations of actions the user just took ("Saved", "Copied").
  • Keep messages under ~1 short sentence; move detail to an in-app log if needed.

Avoid

  • Do not use a toast for anything the user must act on immediately — use a Dialog or Alert.
  • Avoid firing more than one toast per user action.

Skeleton & Spinner

Skeleton / Spinner

Two ways to signal loading. Skeletons preview the shape of content that's about to appear (better for layout stability); spinners indicate indeterminate activity with no known shape.

Skeleton — content shapes

Match the skeleton's shape and size to the content it will be replaced by, to avoid layout shift.

Skeleton — card

A composed loading state for a card-like layout.

Spinner — sizes & inline use

For indeterminate waits: page loads, button submissions, or small inline areas.

Carries role='status' and an aria-label of 'Loading' by default.

Recommended

  • Prefer skeletons for content-heavy areas (feeds, tables, cards) to reduce perceived load time.
  • Use a spinner for short, indeterminate waits inside a button or small region.

Avoid

  • Do not leave a skeleton or spinner visible for more than a few seconds without feedback — add a timeout message.
  • Avoid mixing skeleton and spinner for the same loading region.

Empty State

Empty

Fills the space where content would normally be when there is nothing to show yet — first use, a cleared inbox, or a search with no results — and offers a clear next step.

No data yet

First-run state encouraging the primary action.

No projects yet
Create your first project to get started.

No search results

Confirms the search ran, explains why nothing matched, and offers a way to reset.

No results found
Try adjusting your filters or search terms.

Cleared inbox

A positive, low-key empty state — not every empty state signals a problem.

You're all caught up
No new notifications right now.

Recommended

  • Explain why the area is empty and what to do next, when there is a clear next step.
  • Match the tone to the situation — neutral/positive for "nothing to do", helpful for "nothing found yet".

Avoid

  • Do not leave a blank area with no explanation — it reads as a bug.
  • Avoid a heavy illustration when a simple icon communicates the state just as well.