Skip to Content
Getting Started

Getting Started

Install

npm i @spacing-ui/core # or pnpm add @spacing-ui/core # or yarn add @spacing-ui/core

React 18 or 19 is required (peer dependency).

Your first component

Every primitive is headless — it renders semantic HTML with the correct ARIA attributes, and you style it however you want.

import { Dialog } from "@spacing-ui/core"; export function Example() { return ( <Dialog> <Dialog.Trigger>Open dialog</Dialog.Trigger> <Dialog.Overlay /> <Dialog.Content> <Dialog.Title>Hello</Dialog.Title> <Dialog.Description>This is a headless dialog.</Dialog.Description> <Dialog.Close>Close</Dialog.Close> </Dialog.Content> </Dialog> ); }

That’s it. No styles are applied — Dialog handles the focus trap, escape-to-close, body scroll lock, and ARIA wiring. You handle the look.

Controlled vs uncontrolled

Every stateful primitive supports both patterns. Pass defaultOpen / defaultValue for uncontrolled, or pass open / value with the matching onOpenChange / onValueChange for controlled.

// Uncontrolled <Dialog defaultOpen>...</Dialog> // Controlled const [open, setOpen] = useState(false); <Dialog open={open} onOpenChange={setOpen}>...</Dialog>

Compound components

All primitives use the compound pattern. Sub-parts are namespaced on the root:

<Tabs defaultValue="a"> <Tabs.List> <Tabs.Trigger value="a">A</Tabs.Trigger> <Tabs.Trigger value="b">B</Tabs.Trigger> </Tabs.List> <Tabs.Content value="a">Panel A</Tabs.Content> <Tabs.Content value="b">Panel B</Tabs.Content> </Tabs>

What’s next

Last updated on