A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.
84
Basic form controls including buttons, inputs, checkboxes, switches, and form organization components with built-in accessibility and interaction states.
A basic button component with interaction states and polymorphic rendering.
/**
* Basic button component with interaction states
* @param props - Button properties including standard HTML button attributes
*/
function Button<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: ButtonProps<TTag>
): JSX.Element;
interface ButtonProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Whether the button is disabled */
disabled?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Button type for form submission */
type?: 'button' | 'submit' | 'reset';
/** Render prop providing interaction state */
children?: React.ReactNode | ((props: ButtonRenderProps) => React.ReactNode);
}
interface ButtonRenderProps {
/** Whether button is disabled */
disabled: boolean;
/** Whether button is being hovered */
hover: boolean;
/** Whether button has focus */
focus: boolean;
/** Whether button is being pressed */
active: boolean;
/** Whether button has autofocus */
autofocus: boolean;
}Usage Examples:
import { Button } from "@headlessui/react";
// Basic button
<Button className="px-4 py-2 bg-blue-500 text-white rounded">
Click me
</Button>
// Button with render props for dynamic styling
<Button>
{({ hover, focus, active, disabled }) => (
<span className={`
px-4 py-2 rounded transition-colors
${disabled ? 'bg-gray-300 cursor-not-allowed' : 'bg-blue-500'}
${hover && !disabled ? 'bg-blue-600' : ''}
${focus ? 'ring-2 ring-blue-300' : ''}
${active ? 'bg-blue-700' : ''}
`}>
Click me
</span>
)}
</Button>
// Polymorphic button as link
<Button as="a" href="/profile" className="text-blue-500 underline">
Go to Profile
</Button>A checkbox input component with form integration and indeterminate state support.
/**
* Checkbox input component with form integration
* @param props - Checkbox properties including form attributes
*/
function Checkbox<TType = string>(
props: CheckboxProps<TType>
): JSX.Element;
interface CheckboxProps<TType = string> extends PolymorphicProps<'input'> {
/** Value for form submission */
value?: TType;
/** Whether checkbox is disabled */
disabled?: boolean;
/** Whether checkbox is in indeterminate state */
indeterminate?: boolean;
/** Controlled checked state */
checked?: boolean;
/** Default checked state for uncontrolled usage */
defaultChecked?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Associated form ID */
form?: string;
/** Form field name */
name?: string;
/** Change handler receiving checked state */
onChange?: (checked: boolean) => void;
/** Tab index */
tabIndex?: number;
/** Render prop providing interaction and state */
children?: React.ReactNode | ((props: CheckboxRenderProps) => React.ReactNode);
}
interface CheckboxRenderProps {
/** Current checked state */
checked: boolean;
/** Whether currently changing state */
changing: boolean;
/** Whether has focus */
focus: boolean;
/** Whether being pressed */
active: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether disabled */
disabled: boolean;
/** Whether in indeterminate state */
indeterminate: boolean;
}Usage Examples:
import { useState } from "react";
import { Checkbox } from "@headlessui/react";
// Controlled checkbox
function ControlledExample() {
const [enabled, setEnabled] = useState(false);
return (
<Checkbox checked={enabled} onChange={setEnabled}>
{({ checked }) => (
<span className={`${checked ? 'bg-blue-500' : 'bg-gray-200'}
relative inline-block w-6 h-6 rounded border-2`}>
{checked && (
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</span>
)}
</Checkbox>
);
}
// Uncontrolled checkbox with form integration
<form>
<Checkbox name="newsletter" value="yes" defaultChecked>
Subscribe to newsletter
</Checkbox>
</form>
// Indeterminate checkbox (for "select all" scenarios)
<Checkbox indeterminate={someSelected && !allSelected}>
Select All
</Checkbox>A text input component with form integration and validation states.
/**
* Text input component with form integration
* @param props - Input properties including standard HTML input attributes
*/
function Input<TTag extends keyof JSX.IntrinsicElements = 'input'>(
props: InputProps<TTag>
): JSX.Element;
interface InputProps<TTag extends keyof JSX.IntrinsicElements = 'input'>
extends PolymorphicProps<TTag> {
/** Whether input is disabled */
disabled?: boolean;
/** Whether input is in invalid state */
invalid?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing interaction state */
children?: React.ReactNode | ((props: InputRenderProps) => React.ReactNode);
}
interface InputRenderProps {
/** Whether disabled */
disabled: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether in invalid state */
invalid: boolean;
}Usage Examples:
import { Input, Field, Label, Description } from "@headlessui/react";
// Basic input with styling
<Input
type="email"
placeholder="Enter your email"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
/>
// Input with render props for dynamic styling
<Input>
{({ focus, invalid, hover }) => (
<input
className={`
w-full px-3 py-2 border rounded-md transition-colors
${invalid ? 'border-red-500' : 'border-gray-300'}
${focus ? 'ring-2 ring-blue-500' : ''}
${hover && !focus ? 'border-gray-400' : ''}
`}
placeholder="Username"
/>
)}
</Input>
// Input in form context
<Field>
<Label>Email address</Label>
<Input
type="email"
name="email"
required
className="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<Description>We'll never share your email</Description>
</Field>A toggle switch component with form integration.
/**
* Toggle switch component
* @param props - Switch properties including form attributes
*/
function Switch<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: SwitchProps<TTag>
): JSX.Element;
interface SwitchProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Current checked state */
checked?: boolean;
/** Default checked state for uncontrolled usage */
defaultChecked?: boolean;
/** Change handler receiving checked state */
onChange?: (checked: boolean) => void;
/** Form field name */
name?: string;
/** Form value when checked */
value?: string;
/** Associated form ID */
form?: string;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Whether switch is disabled */
disabled?: boolean;
/** Tab index */
tabIndex?: number;
/** Render prop providing interaction and state */
children?: React.ReactNode | ((props: SwitchRenderProps) => React.ReactNode);
}
interface SwitchRenderProps {
/** Whether switch is checked */
checked: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether being pressed */
active: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether currently changing state */
changing: boolean;
/** Whether disabled */
disabled: boolean;
}Usage Examples:
import { useState } from "react";
import { Switch } from "@headlessui/react";
// Toggle switch with custom styling
function ToggleExample() {
const [enabled, setEnabled] = useState(false);
return (
<Switch checked={enabled} onChange={setEnabled}>
{({ checked }) => (
<span className={`${checked ? 'bg-blue-600' : 'bg-gray-200'}
relative inline-flex h-6 w-11 items-center rounded-full
transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500`}>
<span className={`${checked ? 'translate-x-6' : 'translate-x-1'}
inline-block h-4 w-4 transform rounded-full bg-white transition`} />
</span>
)}
</Switch>
);
}
// Switch with form integration
<form>
<Switch name="notifications" value="enabled" defaultChecked>
Enable notifications
</Switch>
</form>A native select element wrapper with enhanced styling capabilities.
/**
* Native select element wrapper
* @param props - Select properties including standard HTML select attributes
*/
function Select<TTag extends keyof JSX.IntrinsicElements = 'select'>(
props: SelectProps<TTag>
): JSX.Element;
interface SelectProps<TTag extends keyof JSX.IntrinsicElements = 'select'>
extends PolymorphicProps<TTag> {
/** Whether select is disabled */
disabled?: boolean;
/** Whether select is in invalid state */
invalid?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing interaction state */
children?: React.ReactNode | ((props: SelectRenderProps) => React.ReactNode);
}
interface SelectRenderProps {
/** Whether disabled */
disabled: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether being pressed */
active: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether in invalid state */
invalid: boolean;
}A textarea element wrapper with enhanced styling capabilities.
/**
* Textarea element wrapper
* @param props - Textarea properties including standard HTML textarea attributes
*/
function Textarea<TTag extends keyof JSX.IntrinsicElements = 'textarea'>(
props: TextareaProps<TTag>
): JSX.Element;
interface TextareaProps<TTag extends keyof JSX.IntrinsicElements = 'textarea'>
extends PolymorphicProps<TTag> {
/** Whether textarea is disabled */
disabled?: boolean;
/** Whether textarea is in invalid state */
invalid?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing interaction state */
children?: React.ReactNode | ((props: TextareaRenderProps) => React.ReactNode);
}
interface TextareaRenderProps {
/** Whether disabled */
disabled: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether in invalid state */
invalid: boolean;
}A field wrapper that provides form field context and disabled state propagation.
/**
* Field wrapper that provides form field context
* @param props - Field properties
*/
function Field<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: FieldProps<TTag>
): JSX.Element;
interface FieldProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether field is disabled (propagates to children) */
disabled?: boolean;
/** Render prop providing field state */
children?: React.ReactNode | ((props: FieldRenderProps) => React.ReactNode);
}
interface FieldRenderProps {
/** Whether field is disabled */
disabled: boolean;
}A fieldset wrapper for grouping related form fields.
/**
* Fieldset wrapper for grouping form fields
* @param props - Fieldset properties including standard HTML fieldset attributes
*/
function Fieldset<TTag extends keyof JSX.IntrinsicElements = 'fieldset'>(
props: FieldsetProps<TTag>
): JSX.Element;
interface FieldsetProps<TTag extends keyof JSX.IntrinsicElements = 'fieldset'>
extends PolymorphicProps<TTag> {
/** Whether fieldset is disabled (affects all contained fields) */
disabled?: boolean;
/** Render prop providing fieldset state */
children?: React.ReactNode | ((props: FieldsetRenderProps) => React.ReactNode);
}
interface FieldsetRenderProps {
/** Whether fieldset is disabled */
disabled: boolean;
}A legend component for fieldsets (wrapper around Label).
/**
* Legend component for fieldsets
* @param props - Legend properties
*/
function Legend<TTag extends keyof JSX.IntrinsicElements = 'legend'>(
props: LegendProps<TTag>
): JSX.Element;
interface LegendProps<TTag extends keyof JSX.IntrinsicElements = 'legend'>
extends PolymorphicProps<TTag> {
/** Standard legend props inherit from Label */
}Usage Examples:
import { Field, Fieldset, Legend, Label, Input, Checkbox, Description } from "@headlessui/react";
// Form with field organization
<form>
<Fieldset>
<Legend>Personal Information</Legend>
<Field>
<Label>First Name</Label>
<Input name="firstName" required />
<Description>Enter your first name</Description>
</Field>
<Field>
<Label>Last Name</Label>
<Input name="lastName" required />
</Field>
</Fieldset>
<Fieldset disabled={isSubmitting}>
<Legend>Preferences</Legend>
<Field>
<Checkbox name="newsletter">
Subscribe to newsletter
</Checkbox>
</Field>
</Fieldset>
</form>A specialized button that closes its containing component (Dialog, Popover, etc.).
/**
* Button that closes its containing component
* @param props - CloseButton properties (inherits all ButtonProps)
*/
function CloseButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: CloseButtonProps<TTag>
): JSX.Element;
interface CloseButtonProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends ButtonProps<TTag> {
/** All props inherited from Button component */
}Usage Examples:
import { Dialog, DialogPanel, CloseButton } from "@headlessui/react";
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
<DialogPanel>
<h2>Dialog Title</h2>
<p>Dialog content...</p>
{/* Automatically closes the dialog when clicked */}
<CloseButton className="absolute top-2 right-2 p-1">
<XMarkIcon className="w-5 h-5" />
</CloseButton>
</DialogPanel>
</Dialog>// Base polymorphic props for all components
interface PolymorphicProps<TTag extends keyof JSX.IntrinsicElements = 'div'> {
/** Element or component to render as */
as?: TTag;
/** Children content or render prop function */
children?: React.ReactNode | ((props: any) => React.ReactNode);
}
// Common interaction states
interface BaseInteractionState {
disabled: boolean;
hover: boolean;
focus: boolean;
active: boolean;
autofocus: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-headlessui--reactdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10