Skip to content

How contour-ui works

What ships, which package to pick, and how the packages fit together.

Pre-release

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:

PackageWhat you getUse it when
@contour-ui/reactStyled components, everyday and for audio-plugin UIsyou build an app or a plugin UI in the contour-ui look
@contour-ui/headless-reactUnstyled components: structure and behavioryou want contour-ui behavior with your own look
@contour-ui/fumadocsA documentation layout for Fumadocs, built from @contour-ui/reactyou 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 load tokens.css once 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 exports Primitive, 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 of fumadocs-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) and data-part (the part), for example data-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, and contour-<component>__<part> on the other parts. Your className is 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.

Next steps

  1. Install a package and load its stylesheets.
  2. Choose a theme: dark is the default, light is one attribute away.
  3. Use tokens for your own layout so it matches the components.