Overview
Radix UI components with internal state support both controlled and uncontrolled usage patterns, giving you flexibility in how you manage state.This follows the same pattern as form elements in React: you can either let the component manage its own state (uncontrolled) or manage it yourself (controlled).
Controlled vs Uncontrolled
Uncontrolled Components
Uncontrolled components manage their own internal state. You provide an initial value, and the component handles updates. Example: Uncontrolled AccordionControlled Components
Controlled components delegate state management to you. You provide the current value and an onChange handler. Example: Controlled AccordionProp Patterns
All stateful Radix components follow this pattern:How It Works Internally
Radix uses theuseControllableState hook to support both patterns.
From packages/react/use-controllable-state/src/use-controllable-state.tsx:18:
- Detects if a
propvalue is provided (controlled) - Uses internal state if no
prop(uncontrolled) - Calls
onChangein controlled mode - Warns in development if you switch between modes
Component Examples
Dialog
Uncontrolled:packages/react/dialog/src/dialog.tsx:61:
Checkbox
Uncontrolled:Accordion (Multiple Mode)
Uncontrolled:packages/react/accordion/src/accordion.tsx:161:
State Change Callbacks
All controlled components provide callbacks that fire when state changes.Using Callbacks
Common Use Cases
1. Analytics tracking:Finite State Machines
Radix components use enumerated strings for state, not booleans.Why Enums Over Booleans?
❌ Boolean state:- Only two states
- Doesn’t scale to complex states
- Less explicit
- Explicit states
- Easy to extend (add ‘opening’, ‘closing’, etc.)
- Self-documenting
- Type-safe
State Attributes
Components expose state viadata-state attributes:
packages/react/accordion/src/accordion.tsx:372:
Extended States
Some components have richer state:Advanced Patterns
Derived State
Compute values from component state:Coordinated State
Manage multiple components together:State Machines with XState
For complex state logic, integrate with state machine libraries:Best Practices
1. Choose the Right Pattern
Use uncontrolled when:- Simple interactions
- State doesn’t need to be read elsewhere
- No persistence required
- Need to read state
- Sync with other state
- Persist state
- Conditional logic based on state
- Analytics tracking
2. Don’t Switch Between Patterns
❌ Avoid:3. Initialize State Consistently
Ensure default/initial values match:4. Handle Edge Cases in Callbacks
Validate state changes:Summary
Radix UI state management provides:- Flexibility - Choose controlled or uncontrolled
- Consistency - Same pattern across all components
- Callbacks - React to state changes
- Explicit state - Enumerated strings, not booleans
- Type safety - Full TypeScript support
The controlled/uncontrolled pattern gives you the flexibility to start simple and add complexity only when needed.
Related Concepts
- Philosophy - Learn about the principles behind state management
- Composability - Understand how state works with composition
- Customization - Style components based on state