A set of completely unstyled, fully accessible UI components for React Native.
Early stage:
@bareboard/react-nativeis experimental and its API may change without notice. It is published on npm. See Status.
Building accessible interactions is hard: menus, listboxes, dialogs, comboboxes and disclosures each need state, focus handling and the right accessibility roles. Headless UI solves this for the web by providing those behaviours without any styles, so you keep full control over the look.
@bareboard/react-native brings that approach to React Native. It provides the same kind of components, with the same composition model, and leaves every visual decision to your app:
- Unstyled: components render your children and accept
styleprops. Nothing is imposed on the look. - Stateful by render prop: components expose their state, such as
open, to the children so the UI can react to it. - Accessible by default: accessibility state and roles are set for you, and the caller can still provide its own
accessibilityState.
@bareboard/react-native is a React Native port of Headless UI, created by Tailwind Labs. It is not affiliated with or endorsed by Tailwind Labs.
This repository was created from tailwindlabs/headlessui, but its history starts at the React Native scaffold: the earlier history, including the React and Vue packages, is not on main.
@bareboard/react-native is experimental. Version 0.2.1 is published on npm as a public package. The behaviour is covered by unit tests, and a Playwright test checks the combobox on the web. Testing on real devices is still to come.
@bareboard/react-native keeps the components, the state machines and the accessibility roles of Headless UI. Some parts of the web API have no equivalent in React Native and are not implemented:
- Positioning:
anchorpositions a portalled panel against its trigger (see below), but anchored panels do not flip when they reach the edge of the screen. The other overlays of the playground are placed with absolute positioning. - Transitions:
Transitionand thetransitionprop animateopacityandscale, with durations in milliseconds (enterandleave). CSS class swaps and theappearprop are not supported.unmountis supported. Thetransitionprop is on the panels of Disclosure, Popover, Menu, Listbox and Combobox, and on Dialog, which animates its whole content. - Polymorphism: there is no
asprop. Components render React Native primitives, and the app chooses the children. - Portal and modal: the
portalprop of Menu, Popover, Listbox and Combobox renders the panel above the rest of the app, and keeps its React context. Inside aPortalGroup, the panel is drawn by the group, which lets touches through to the page below. Without a group, it is drawn in aModal, which covers the page, so touches and screen readers stay in the panel. Themodalprop draws a full-screen backdrop that closes the panel on press, and only applies withportal. Theanchorprop places a portalled panel against its trigger:bottom,bottom start,bottom end,top,top startortop end. The panel takes the width of the trigger as its minimum width. MountPortalGrouponce, around the whole app. Itstargetref receives the view the portals are drawn in, to measure it. Withtransitionin aModal, the leave animation is cut short, because theModalhides at once. ThePortalandPortalGroupcomponents are exported separately. - Auto focus:
autoFocusworks onInput,TextareaandComboboxInput, where it is the nativeTextInputprop. Other components do not take focus on mount: React Native has no keyboard focus outside of web builds, so theirautoFocusprop is not implemented. - Form fields: there are no
nameandformprops on Checkbox, Switch, RadioGroup, Listbox and Combobox, because there is no form submission to feed. - Forms: there is no
<form>element and noFormDatahelper. - Devices: the behaviour has not been tested on iOS or Android devices yet.
| Name | Location | Description |
|---|---|---|
@bareboard/react-native |
packages/react-native |
Headless UI components for React Native |
playground-expo-uniwind |
playgrounds/expo-uniwind |
Expo app styled with Uniwind, with examples for each component |
The components include Button, Checkbox, Combobox, Dialog, Disclosure, Listbox, Menu, Popover, RadioGroup, Select, Switch, Tabs, Textarea and Transition, among others.
Install the package from npm, with the React and React Native versions your app already uses:
pnpm add @bareboard/react-nativenpm install @bareboard/react-nativereact (18 or 19) and react-native are peer dependencies: your app provides them. See the package README for more details.
This is a simplified version of the disclosure example from the playground, without its styles. Components are imported from the package, and the styles are yours.
import { Disclosure, DisclosureButton, DisclosurePanel } from '@bareboard/react-native'
import { Text } from 'react-native'
export function ShippingDetails() {
return (
<Disclosure>
{({ open }) => (
<>
<DisclosureButton>
<Text>{open ? 'Hide' : 'Show'} the shipping details</Text>
</DisclosureButton>
<DisclosurePanel>
<Text>Orders ship within two business days.</Text>
</DisclosurePanel>
</>
)}
</Disclosure>
)
}Inside this repository, the playground imports the package as a workspace dependency.
To work on the repository, you need Node.js 24.15 or newer, and pnpm. With Corepack, corepack enable provides the pnpm version pinned in package.json.
git clone https://github.com/jbrxl/bareboard.git
cd bareboard
pnpm installThen run the checks, or open the playground:
pnpm check
pnpm test
pnpm devThe root package.json holds the commands used across the workspaces. pnpm lint and pnpm typecheck run the scripts of every package in turn.
See CONTRIBUTING.md for the coding standards, the test commands and the playground. AGENTS.md describes the layout and conventions of the repository.
If you're interested in contributing, please read our contributing docs before submitting a pull request.
MIT. Headless UI was originally created by Tailwind Labs and is licensed under the MIT License. See LICENSE.