How contour-ui works
What ships, which package to pick, and how the packages fit together.
The packages are not on npm yet. Essentials and Headless have their first components and Fumadocs runs this site; the Audio controls come next, and more components arrive section by section.
Pick a package
contour-ui ships three React packages. Pick by what you build and how much of the look you want to own:
| Package | What you get | Use it when |
|---|---|---|
@contour-ui/react | Styled components, everyday and for audio-plugin UIs | you build an app or a plugin UI in the contour-ui look |
@contour-ui/headless-react | Unstyled components: structure and behavior | you want contour-ui behavior with your own look |
@contour-ui/fumadocs | A documentation layout for Fumadocs, built from @contour-ui/react | you build a docs site on Fumadocs |
@contour-ui/react holds both the everyday components, in Essentials, and the
controls of audio-plugin UIs, in Audio, so a plugin needs one package for its whole
window. A docs site on Fumadocs shows the same components in its pages.
How the packages fit together
From the foundation up:
- Tokens (
@contour-ui/tokens) are the design decisions as CSS custom properties: colors, spacing, sizes, radii, type and motion, for every theme. The styled components read them. You loadtokens.cssonce and can use the same variables in your own CSS, see Styling with tokens. - Icons (
@contour-ui/icons) are the icon set as data, with no React: Lucide icons and brand logos as paths for SVG and Canvas 2D. The Icon component draws them as SVG. - Core (
@contour-ui/core) is the logic of the components, with no React and no DOM: state machines, collections, positioning, scales and the formatting of values. - Headless (
@contour-ui/headless-react) runs that logic in React: it renders the structure and behavior of interactive components (popovers, dialogs, listboxes…), with no styles. It also exportsPrimitive, the elements every part renders through, so you can build parts of your own. - The styled package (
@contour-ui/react) styles them: an interactive styled component is its Headless component plus class names and the CSS behind them, with the same props. Components without behavior, such as buttons, cards and alerts, exist only here. Its components come in two sections: Essentials, the everyday parts of any interface, and Audio, the controls of audio-plugin UIs, with audio-specific styles such as monospace numeric readouts whose digits do not shift while the value changes. - Fumadocs (
@contour-ui/fumadocs) builds a documentation site from the styled components on Fumadocs: the layout, the search and the components of MDX pages, in place offumadocs-ui. This site runs on it, see Fumadocs.
All packages are released together under one version: keep their versions equal.
Anatomy of a component
Each part of a component renders one DOM element. A component made of a single element is a plain
component, such as <Button> or <Kbd>. A component with several parts is a namespace of parts,
such as <Card.Root> with <Card.Title> inside:
import { Button, Card } from '@contour-ui/react';
<Card.Root className="session">
<Card.Body>
<Card.Title>Session</Card.Title>
<Card.Description>48 kHz, 24-bit</Card.Description>
</Card.Body>
<Card.Footer>
<Button size="sm">Open</Button>
</Card.Footer>
</Card.Root>;- Parts accept the props of their element (
className,style,aria-*, event handlers, …) and pass them through. - With
asChild, a part renders its only child instead of its own element and merges its props, event handlers and ref into it. Use it to render your router's link in place of an<a>, for example. - Every part is marked with
data-scope(the component) anddata-part(the part), for exampledata-scope="card" data-part="title". Headless users style with these attributes; on styled components they are stable hooks for tests and overrides. - Styled components add class names:
contour-<component>on the root or the single element, andcontour-<component>__<part>on the other parts. YourclassNameis merged with them. - Variants and sizes are attributes next to the state ones:
data-variant,data-size,data-state. A selector such as.contour-button[data-variant='solid']targets one variant.