@headlessui/react is a set of completely unstyled, accessible UI components designed to integrate with Tailwind CSS. @radix-ui/react-checkbox is a low-level primitive for building accessible checkboxes without enforcing styles. react-checkbox-tree is a specialized library for rendering hierarchical checkbox structures with built-in parent-child selection logic.
Choosing the right tool for checkboxes depends on whether you need a single input, a full design system primitive, or a complex tree structure. @headlessui/react, @radix-ui/react-checkbox, and react-checkbox-tree each solve different parts of this problem. Let's look at how they handle implementation, state, and nested data.
@radix-ui/react-checkbox provides a dedicated primitive.
// radix: Dedicated component
import { Checkbox } from '@radix-ui/react-checkbox';
<Checkbox.Root className="CustomCheckbox" id="c1">
<Checkbox.Indicator className="CheckboxIndicator">
{/* Check icon */}
</Checkbox.Indicator>
</Checkbox.Root>
@headlessui/react does not ship a Checkbox component.
// headless: Native input + Tailwind
<input
type="checkbox"
className="rounded border-gray-300 text-indigo-600"
onChange={(e) => console.log(e.target.checked)}
/>
react-checkbox-tree is designed for trees, not single inputs.
// tree: Node structure required
import CheckboxTree from 'react-checkbox-tree';
<CheckboxTree
nodes={[{ value: '1', label: 'Single Item', children: [] }]}
checked={[]}
expanded={[]}
/>
@radix-ui/react-checkbox uses a controlled callback.
onCheckedChange prop fires when the state toggles.// radix: State callback
<Checkbox.Root
checked={checked}
onCheckedChange={(next) => setChecked(next)}
/>
@headlessui/react relies on standard React events.
useState and the onChange event.// headless: Standard onChange
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
react-checkbox-tree manages internal expansion state.
checked and expanded arrays.onCheck callback returns an array of values.// tree: Array-based state
<CheckboxTree
nodes={nodes}
checked={checked}
expanded={expanded}
onCheck={(values) => setChecked(values)}
onExpand={(values) => setExpanded(values)}
/>
@radix-ui/react-checkbox has no built-in tree logic.
// radix: Manual recursion needed
// You must write logic to check all children when parent is clicked
const handleParentCheck = (parentId) => {
// Custom logic to update children state
};
@headlessui/react has no built-in tree logic.
// headless: Manual recursion needed
// Map over nodes and render inputs recursively
const renderNode = (node) => (
<div>
<input type="checkbox" />
{node.children.map(renderNode)}
</div>
);
react-checkbox-tree includes tree logic out of the box.
// tree: Built-in hierarchy
// Parent/child relationships work automatically
<CheckboxTree
nodes={nodes}
checked={checked}
onCheck={setChecked}
/>
@radix-ui/react-checkbox is unstyled by design.
Root and Indicator./* radix: Custom CSS */
.CustomCheckbox { all: unset; display: flex; }
.CheckboxIndicator { color: white; }
@headlessui/react expects Tailwind CSS.
<!-- headless: Tailwind utilities -->
<input class="h-4 w-4 rounded border-gray-300" />
react-checkbox-tree requires specific CSS files.
// tree: Import required CSS
import 'react-checkbox-tree/lib/react-checkbox-tree.css';
| Feature | @radix-ui/react-checkbox | @headlessui/react | react-checkbox-tree |
|---|---|---|---|
| Component Type | Primitive | Suite (No Checkbox) | Specialized Tree |
| Styling | Unstyled (CSS-in-JS/CSS) | Tailwind Utilities | Required CSS File |
| Tree Logic | Manual | Manual | Built-in |
| Best For | Design Systems | Tailwind Projects | Nested Data |
@radix-ui/react-checkbox is the best choice for building a single, accessible checkbox within a custom design system. It gives you full control over the markup and behavior without forcing styles.
@headlessui/react is ideal if you are already committed to Tailwind CSS. Since it lacks a dedicated Checkbox component, you will use native inputs styled with Tailwind, which keeps your bundle small and simple.
react-checkbox-tree is the clear winner for hierarchical data. If you need parent-child selection logic, this library saves you from writing complex recursion code, though it comes with stricter styling requirements.
Final Thought: Match the tool to the data shape. Use Radix or Headless for flat lists and single inputs. Reach for react-checkbox-tree only when your data is actually nested.
Choose @headlessui/react if you are already using Tailwind CSS and want components that match its utility-first workflow. Note that it does not include a dedicated Checkbox component, so you will likely use native inputs styled with Tailwind or the Switch component for toggles.
Choose @radix-ui/react-checkbox if you need a robust, accessible single checkbox primitive that you can style completely from scratch. It is ideal for design systems where you need full control over the HTML structure and behavior without predefined styles.
Choose react-checkbox-tree if your primary requirement is displaying nested data with parent-child selection relationships. It saves significant development time on recursion logic but brings in specific styling dependencies and less flexibility for single inputs.
A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.
npm install @headlessui/react
For full documentation, visit headlessui.dev.
For help, discussion about best practices, or feature ideas: