The controlled/uncontrolled distinction isn't just about form inputs — it applies to any stateful component you build. This lesson covers the general tradeoff between letting a component own its state internally versus having a parent control it, and how to design a component that supports both.
1Beyond Forms: A General Component Design Question
The controlled/uncontrolled choice extends to any stateful component — an Accordion, Tabs, or Modal. The core question is always the same: should the component manage its own internal state, or should a parent own that state and pass it down as props? This is a consequential, general component API decision, not just a forms-specific one.
2Uncontrolled: The Component Owns Its State
An uncontrolled component manages its own state internally with useState, exposing only an optional starting value like defaultOpen. The consumer can't directly read or change the value from outside afterward — simpler to use, but the parent has no ability to react to or override changes.
3Controlled: The Parent Owns the State
A controlled component receives its current value and a change handler as props, with no internal useState of its own. Every change is routed through the parent: the child calls the handler, the parent updates its own state, and the new value flows back down as a prop.
4Why Bother Controlling It? Coordination.
The primary reason to make a component controlled is cross-component coordination — an accordion group where opening one item must close the others, or a modal that must close in response to an unrelated event like a route change. This kind of coordination is only possible if a parent actually owns the relevant state.
5The Best of Both: Supporting Both Modes
A well-designed reusable component can support both patterns: if a controlling prop like isOpen is provided, the component behaves as fully controlled; if omitted, it falls back to managing its own internal state initialized from a default value. This offers a simple default API while still allowing full external control when genuinely needed.
6Step-by-Step Breakdown
Beyond Forms: A General Component Design Question. You've seen controlled vs. uncontrolled inputs in the Forms module — but this same choice applies to any stateful component you build: an Accordion, a Tabs widget, a Modal. Should the component manage its own open/closed state internally, or should a parent own that state and pass it down? This is one of the most consequential decisions in component API design.
Uncontrolled: The Component Owns Its State. An uncontrolled component manages its own state internally with useState, exposing only an optional defaultValue to set the starting point. The consumer can't directly read or change the value from outside — this is simpler to use, at the cost of the parent having no way to react to or control changes.
In an uncontrolled <Accordion defaultOpen={true} />, can a parent component directly force the accordion closed later from outside?
- →No — the state lives entirely inside the component
- →Yes — defaultOpen updates the state whenever it changes
Controlled: The Parent Owns the State. A controlled component receives its current value AND a change handler as props, and never manages that value with its own useState. Every change goes through the parent: the child calls onToggle, the parent updates its own state, and the new value flows back down as a prop.
Why Bother Controlling It? Coordination.. The main reason to make a component controlled is coordination with other state: an accordion that must close automatically when a different one opens (an 'exclusive' accordion group), or a modal that needs to close in response to a route change. A parent can only enforce those rules if it actually owns the state.
Why does an 'exclusive' accordion group (opening one closes all others) require a controlled design?
- →The parent must own every item's state to enforce that only one is open at a time
- →Controlled components always render faster
The Best of Both: Supporting Both Modes. Well-designed reusable components often support both patterns at once. If an isOpen prop is provided, the component behaves as fully controlled; if it's omitted, the component falls back to managing its own internal state, initialized from defaultOpen. This gives simple use cases a simple API, while still allowing full control when it's genuinely needed.
Mastery Achieved. You now understand this decision beyond just forms: uncontrolled components are simpler but isolated, controlled components enable cross-component coordination at the cost of more setup, and a well-designed reusable component can often support both. Next, you'll learn the Render Props pattern for sharing logic through a function-as-child.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
This is a component API design pattern, not a browser feature.
Fully applicable.
Fully applicable.
Fully applicable.
Accessibility (A11y)
1Controlled State Enables Correct Focus Coordination
Widgets like accordion groups or tab panels that need to manage focus correctly across multiple items (e.g. moving focus when one panel closes) generally require controlled state so the parent can orchestrate focus changes deliberately.
SEO Implications
- 1
This Pattern Affects Client Interaction, Not Server-Rendered Content
Whether a component is controlled or uncontrolled governs client-side interactivity after hydration; it has no bearing on what's present in the initial server-rendered HTML.
Best Practices
Default to Uncontrolled for Simple, Self-Contained Widgets
If nothing outside the component genuinely needs to read or coordinate its state, an uncontrolled design keeps the consuming code simpler — only add controlled support when a real coordination need arises.
Never Silently Switch a Component Between Controlled and Uncontrolled
A component shouldn't have an isOpen prop that's sometimes defined and sometimes undefined across renders — React will warn about switching between controlled and uncontrolled, since it produces inconsistent, hard-to-predict behavior.
Frequent Bugs
A React warning appears: 'A component is changing an uncontrolled input to be controlled.'
A prop like isOpen or value started as undefined (uncontrolled) and later received a defined value (controlled) across renders. Ensure the prop is either always defined or always undefined for a given component instance, using a fallback default value if needed.
An accordion group meant to keep only one section open at a time doesn't actually enforce that.
Each accordion item is likely managing its own internal state (uncontrolled), so the parent group has no way to close other items when one opens. Convert the items to controlled components, with the parent group owning which item id is currently open.
Real-World Examples
A Hybrid Modal Component
A design system's Modal component is used simply in most places (uncontrolled, opened via an internal trigger) but needs full external control in one specific flow where it must close automatically after a successful API call. Supporting both an optional isOpen/onClose controlled API and an internal fallback state let both use cases share the same component.
function Modal({ isOpen: controlledOpen, onClose, defaultOpen = false, children }) {
const [internalOpen, setInternalOpen] = useState(defaultOpen);
const isControlled = controlledOpen !== undefined;
const isOpen = isControlled ? controlledOpen : internalOpen;
const close = () => (isControlled ? onClose?.() : setInternalOpen(false));
return isOpen ? <div className="modal" onClick={close}>{children}</div> : null;
}