Skip to main content

What is headless?

A headless component defines how an interactive component works without prescribing how it looks.

USWDS + Tailwind builds on Zag, which models component interactions with state machines. A shared interaction logic can be connected to different frameworks and styled for different design systems.

Components as state

Our headless components are unstyled, but they have a defined structure and behavior. This ensures that component behavior is consistent, regardless of the design system they’re applied to.

Visual style
Select a button to update the expanded value and reveal its panel.
Machine state
{
  "props": {
    "multiple": true,
    "defaultValue": [
      "one"
    ]
  },
  "context": {
    "value": [
      "one"
    ]
  }
}

Build your own

You can use predefined state machines to build your own components. The following example shows how to implement an accordion component in React using the @uswds-tailwind/accordion-compat package.

Zag provides adapters for other frameworks, including Vue, Solid, and Svelte. USWDS + Tailwind uses the Vanilla JavaScript and React adapters.

import * as accordion from '@uswds-tailwind/accordion-compat'
import { normalizeProps, useMachine } from '@zag-js/react'
import { useId } from 'react'
const items = [
{ value: 'one', label: 'First item', content: 'First panel.' },
{ value: 'two', label: 'Second item', content: 'Second panel.' },
]
export function MyAccordion() {
const id = useId()
const service = useMachine(accordion.machine, {
id,
multiple: true,
defaultValue: ['one'],
})
const api = accordion.connect(service, normalizeProps)
return (
<div className="accordion" {...api.getRootProps()}>
{items.map(item => (
<div className="accordion__item" {...api.getItemProps(item)} key={item.value}>
<button className="accordion__button" {...api.getItemTriggerProps(item)}>
{item.label}
</button>
<div className="accordion__panel" {...api.getItemContentProps(item)}>
{item.content}
</div>
</div>
))}
</div>
)
}