Skip to main content

Primitives

Package: @zuilib/primitives · React 18 · Headless UI v2 · Tailwind v4 (optional at runtime)

@zuilib/primitives is the component layer of ZUI: forty-odd primitives that companies build their own design system on. Every visual decision in them reads a token from @zuilib/tokens, so customisation is CSS-variable overrides on :root or on any subtree, and nothing else. Behaviour and accessibility come from Headless UI; the components add the ZUI styling, sizes, form-state wiring and data-slot hooks for your own CSS.

Loading example
pnpm add @zuilib/primitives
import '@zuilib/primitives/zui.css' // or one of the Tailwind entries, see Getting started
import Button from '@zuilib/primitives/button'

What you get

One import per component@zuilib/primitives/button, /input, /dialog, …: tree-shakes to what you use. Compound parts are statics (Select.Option) and named exports
Tokens onlyNo raw colours, radii or sizes in the components. Override --primary, --radius, --control-height-md and every button, field and card follows
Headless UI statedata-open, data-checked, data-focus, data-disabled, data-invalid on the parts they belong to, plus data-state where Headless UI has nothing
data-slot on every part[data-slot="select-option"]: target a part from your own CSS without depending on class names
FormsField gives its control the id, label, description, error and disabled state; every field has invalid, size, fullWidth, name
RSC-safeEvery file starts with 'use client'; import from a server component and it just works
Dark modeThe dark class on <html> (or any wrapper) swaps the token values; the components do nothing special
TranslatableEvery built-in string is a prop and a key of LabelsProvider, so an app translates once
Telemetry, opt-inTelemetryProvider receives open / select / change events from Dialog, Menu, Tabs, Combobox, CommandPalette and tracked Buttons; nothing is emitted without it

Start with Getting started for the stylesheet choice, then Theming for how a brand is applied. The Registry page covers installing single components as source with the zui CLI.

Small screens and touch

The primitives adapt below the sm breakpoint (40rem) and on a coarse pointer without a prop:

  • Overlays fit the phone. A Dialog is the viewport minus a 1rem gutter with a scrolling body; a start / end Drawer is full-width and a top / bottom one at most 85dvh; the CommandPalette becomes a full-width sheet pinned to the top of the viewport. Drawer, the full Dialog and toasts pad past env(safe-area-inset-*).
  • Panels stay on screen. Select, Combobox, Menu, Popover and Tooltip panels never exceed 100vw - 2rem and cap their height at min(240px, 100dvh - 2rem).
  • 44px targets. On a coarse pointer, tabs, menu items, list options and the Table sort button are at least 44px tall; Button, the close and clear buttons, the Checkbox box, Radio indicator, Switch track and Slider thumb take a 44 × 44 hit area without changing size. touchTargetClasses and touchHitAreaClasses from @zuilib/primitives/lib/sizes give your own controls the same.
  • No zoom on focus. size="sm" fields type at 16px below sm, so iOS does not zoom when they take focus; desktop stays at 14px (md was already 16px).
  • Layout steps down. Below sm, md and lg Card (and Accordion) padding step down to the next token; Table's container is min-w-0 max-w-full and scrolls horizontally on its own; Tabs lists scroll horizontally with a hidden scrollbar on phones.

Inventory

Actions

  • Button: variants, tones, sizes, loading, polymorphic as
  • AIButton: Button with a sparkle and a generating state

Forms

Overlays

  • Dialog: modal with backdrop, title and description
  • Drawer: a side panel
  • Popover: anchored panel
  • Menu: actions list with keyboard navigation
  • Tooltip: hover / focus hint
  • CommandPalette: ⌘K launcher with groups, recent items and a global shortcut

Feedback

Data display

Layout

Typography

Companions

@zuilib/data-grid (server-driven grid), @zuilib/charts, @zuilib/ai and @zuilib/apps builds on these components and shares the same tokens. The text editor too.