@headlessui/react vs @radix-ui/react-checkbox vs react-checkbox-tree
Implementing Accessible Checkbox Patterns in React
@headlessui/react@radix-ui/react-checkboxreact-checkbox-treeSimilar Packages:

Implementing Accessible Checkbox Patterns in React

@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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
@headlessui/react028,6291.02 MB1012 months agoMIT
@radix-ui/react-checkbox018,99967 kB4067 days agoMIT
react-checkbox-tree07314.19 MB8925 days agoMIT

Implementing Accessible Checkbox Patterns in React

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.

🧱 Building a Single Checkbox

@radix-ui/react-checkbox provides a dedicated primitive.

  • It handles accessibility attributes (ARIA) automatically.
  • You must supply your own CSS for visuals.
// 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.

  • The recommended approach is using a native HTML input.
  • You style it using Tailwind CSS utilities.
// 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.

  • Using it for a single item is possible but overkill.
  • It requires a specific node structure even for one item.
// tree: Node structure required
import CheckboxTree from 'react-checkbox-tree';

<CheckboxTree
  nodes={[{ value: '1', label: 'Single Item', children: [] }]}
  checked={[]}
  expanded={[]}
/>

🔄 Managing State Changes

@radix-ui/react-checkbox uses a controlled callback.

  • The onCheckedChange prop fires when the state toggles.
  • It passes a boolean or 'indeterminate' string.
// radix: State callback
<Checkbox.Root
  checked={checked}
  onCheckedChange={(next) => setChecked(next)}
/>

@headlessui/react relies on standard React events.

  • You manage state using useState and the onChange event.
  • This gives you direct access to the event object.
// headless: Standard onChange
<input
  type="checkbox"
  checked={checked}
  onChange={(e) => setChecked(e.target.checked)}
/>

react-checkbox-tree manages internal expansion state.

  • You must track both checked and expanded arrays.
  • The 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)}
/>

🌲 Handling Hierarchical Data

@radix-ui/react-checkbox has no built-in tree logic.

  • You must write recursion to handle parent-child checking.
  • Best for custom designs where you build the tree layout yourself.
// 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.

  • You build the tree structure using standard React mapping.
  • You are responsible for all selection algorithms.
// 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.

  • Checking a parent automatically checks children.
  • Indeterminate states are handled internally.
// tree: Built-in hierarchy
// Parent/child relationships work automatically
<CheckboxTree
  nodes={nodes}
  checked={checked}
  onCheck={setChecked}
/>

🎨 Styling and Customization

@radix-ui/react-checkbox is unstyled by design.

  • You add class names to Root and Indicator.
  • No CSS files are required, just your own styles.
/* radix: Custom CSS */
.CustomCheckbox { all: unset; display: flex; }
.CheckboxIndicator { color: white; }

@headlessui/react expects Tailwind CSS.

  • You use utility classes directly in the JSX.
  • No separate CSS files needed if using Tailwind.
<!-- headless: Tailwind utilities -->
<input class="h-4 w-4 rounded border-gray-300" />

react-checkbox-tree requires specific CSS files.

  • You must import the library's CSS or override it.
  • Less flexible if you want a completely unique look.
// tree: Import required CSS
import 'react-checkbox-tree/lib/react-checkbox-tree.css';

📊 Summary: Key Differences

Feature@radix-ui/react-checkbox@headlessui/reactreact-checkbox-tree
Component TypePrimitiveSuite (No Checkbox)Specialized Tree
StylingUnstyled (CSS-in-JS/CSS)Tailwind UtilitiesRequired CSS File
Tree LogicManualManualBuilt-in
Best ForDesign SystemsTailwind ProjectsNested Data

💡 The Big Picture

@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.

How to Choose: @headlessui/react vs @radix-ui/react-checkbox vs react-checkbox-tree

  • @headlessui/react:

    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.

  • @radix-ui/react-checkbox:

    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.

  • react-checkbox-tree:

    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.

README for @headlessui/react

@headlessui/react

A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.

Total Downloads Latest Release License

Installation

npm install @headlessui/react

Documentation

For full documentation, visit headlessui.dev.

Community

For help, discussion about best practices, or feature ideas:

Discuss Headless UI on GitHub