Headless
Unstyled React components with the structure and behavior of contour-ui, for your own look.
Headless components render the DOM structure and behavior of interactive components, and nothing
else: no CSS, no tokens, no themes. The styled components of
@contour-ui/react are the same components with the look of contour-ui, so switching one between
the two packages keeps its parts and props. Components without behavior, such as buttons and
cards, exist only in the styled package.
Setup
pnpm add @contour-ui/headless-reactThere is no stylesheet to load. React 19 and React DOM 19 are peer dependencies.
Usage
import { Placeholder } from '@contour-ui/headless-react';
export function Note() {
return <Placeholder.Root className="note">Unstyled</Placeholder.Root>;
}Styling
Style parts with your own classes, or target them by their data attributes. Every part carries
data-scope (the component) and data-part (the part):
[data-scope='placeholder'][data-part='root'] {
padding: 8px 12px;
border: 1px solid currentColor;
border-radius: 4px;
}Want the contour-ui look for some of your own pieces? The token variables work with Headless too:
load @contour-ui/tokens/tokens.css and see Styling with tokens.
Build your own parts
Every part renders through Primitive: Primitive.div, Primitive.button, Primitive.a and the
other elements parts are made of. A primitive takes its element's props and ref, plus asChild,
which renders the only child instead and merges the props into it:
import { Primitive } from '@contour-ui/headless-react';
import { Link } from 'react-router';
<Primitive.a asChild className="nav-link">
<Link to="/docs">Docs</Link>
</Primitive.a>;When a part combines its own props with the ones it receives, use the same helpers:
mergeProps(...sources)runs every event handler (the last source's first), joins class names, merges styles, and lets the last value win for anything else.composeRefs(...refs)feeds one element to several refs; inside a component,useComposedRefs(first, second)keeps the combined ref stable between renders.
Components
A button that copies a text, with the copied state for your styles and for screen readers.
A button that shows and hides a region, with its size in CSS variables for a height animation.
A window over the page that keeps focus inside, and closes on Escape or a press outside.
A list of options with a highlight the keys move, selection, typeahead and an input that drives it.
Stand-in that shows the shape of every Headless component.
A dialog that opens next to its trigger, closes on Escape or a press outside, and moves focus in and back.
Options of which one is checked, on native radio buttons, with an indicator that follows it.
Sections shown one at a time, with a list of tabs the arrow keys move through and an indicator.
A table of contents that marks the sections in view, with a running indicator.
Utilities
Building blocks of the overlays, for parts of your own too.