What is Headless components?

Headless components: Headless components provide behavior, state and accessibility for UI widgets without any styles, so you supply all of the visual design yourself.

A headless component separates what a widget does from how it looks. The library handles the hard, invisible parts: open and closed state, focus management, keyboard navigation, ARIA roles and attributes, typeahead, positioning and dismissal. You handle the visible parts: classes, layout, color and animation. The result is accessible behavior you did not have to write, with a visual design that matches your product instead of the library's.

How it works

Headless libraries come in two shapes. Component-based libraries, such as Base UI, Radix UI, React Aria Components and Headless UI, give you unstyled parts that render DOM elements you put classes on. Hook-based libraries, such as React Aria's hooks, Downshift or TanStack Table, return props and state that you spread onto your own elements. Both expose state for styling, usually as data attributes:

tsx
import { Switch } from "@base-ui/react/switch"

export function Toggle() {
  return (
    <Switch.Root className="h-5 w-9 rounded-full bg-sunken data-checked:bg-accent">
      <Switch.Thumb className="block size-4 translate-x-0.5 rounded-full bg-surface transition-transform data-checked:translate-x-4.5" />
    </Switch.Root>
  )
}

Why it matters

  • Accessibility is hard to get right twice. A menu alone needs arrow-key navigation, typeahead, Escape to close, focus return and correct aria-expanded wiring. Headless libraries encode the WAI-ARIA Authoring Practices and test them across screen readers.
  • No style overrides. Styled libraries make you fight specificity or theme objects. Headless ones have nothing to fight.
  • Any styling approach. Tailwind CSS, CSS modules or plain CSS all fit.
  • Reusable knowledge. You learn the behavior API once and apply it to very different designs.

Common pitfalls

Headless does not mean finished. You still own visible focus styles, color contrast, touch target sizes, reduced motion and responsive layout. It is easy to remove an outline and never add a :focus-visible replacement. Also check that your styling does not hide state, for example a selected tab that differs from the others only by a subtle color change. Finally, read the library's docs on portals: popups render at the end of <body>, so styles scoped to a parent, such as a theme class on a wrapper, may not reach them.

How MiniDev UI uses them

MiniDev UI is a styled layer on top of headless primitives. Components like Dialog, Select, Dropdown menu and Accordion wrap Base UI parts and add the kit's tokens, shadows, focus rings and motion. You get tested behavior and a finished look, and because the component is installed as source, you can still change either.

SelectuiSelect dropdown on Base UI with a trigger in three sizes, an animated popup, groups, labels, separators, a check indicator and scroll arrows. Shadcn-compatible.npx shadcn@latest add ui.minidev.pro/r/select.json