Headless
@arun-dev/headless ships behaviour, keyboard handling and accessibility, and nothing else — no
CSS, no class names, no colour. Components project their state onto the DOM as data-*
attributes, so any styling approach reads them: plain CSS, CSS modules, utility classes, or a
component library of your own.
npm install @arun-dev/headlessPeer dependencies: react >= 19, react-dom >= 19. Nothing else — it has no runtime
dependencies and loads no stylesheet.
Where it sits
Section titled “Where it sits”flowchart TB
subgraph behaviour["@arun-dev/headless"]
direction TB
B1["Button · Switch"]
B2["useRender · mergeProps · useControlled"]
end
subgraph tokens["@arun-dev/tokens"]
T1["CSS custom properties"]
end
ui["@arun-dev/ui<br/>class names + component CSS"]
behaviour -- "data-* attribute names" --> ui
tokens -- "custom property names" --> ui
classDef pkg fill:transparent,stroke-dasharray:4 3
class behaviour,tokens pkgNothing in @arun-dev/headless knows that @arun-dev/ui exists. The only thing the two agree on
is the spelling of the data-* attributes, which is why you can put your own styling layer in
that position instead.
Two ideas do most of the work
Section titled “Two ideas do most of the work”render lets you replace the element while keeping the behaviour, so a component never has to
guess which tag or which library you need.
data-* attributes carry state out to CSS, so styling reacts to checked or disabled
without the component owning a single class name.
Everything else on these pages is a consequence of those two.
Components are made of parts
Section titled “Components are made of parts”Switch.Root and Switch.Thumb are the two parts of Switch. A part is a component in its
own right: it renders one element and takes its own className, ref and render.
<Switch.Root> <Switch.Thumb /></Switch.Root>Parts are not independent. The root owns the state and shares it through context, so they cannot disagree about whether the switch is checked, and you cannot set one without the other following.
The word is used in that sense throughout these pages. A component with nothing to compose —
Button — is a single part, and you write it as one element.
The render engine
What happens on one render, and the five primitives it is built from.
Prop merging
Precedence, handler order, and how to cancel a component's own handler.
State
Controlled versus uncontrolled, and how state becomes an attribute.
Components
Button, Switch, Checkbox, Dialog, Menu, Combobox and the rest — what each provides and when you need it.
Already-styled components
Section titled “Already-styled components”One styling layer built on these primitives is @arun-dev/ui, which uses
@arun-dev/tokens for its colours. The two packages are
independent — take whichever fits, and use both if you want the styled components in most places
and raw behaviour where you need something custom.