Controlled vs Uncontrolled Components
Controlled components hold their state outside themselves — a parent manages the value, and the component is told what to render. The parent listens for change events and updates the value. Uncontrolled components manage their own internal state — set it once, and the component handles it from there.
A controlled text input in React looks like:
<Input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
Uncontrolled:
<Input defaultValue="initial" />
In a Design System, this choice shapes the whole component API. Controlled components are more predictable — the parent always knows the current state. They're easier to validate, combine with other logic, and test. But they require more boilerplate.
Uncontrolled components are simpler for one-off uses. Drop them in, they work. But coordinating across multiple uncontrolled components gets messy — you can't easily validate a form's state without querying the DOM.
Most modern systems offer both. A Form component in a Design System might use controlled inputs internally (to power validation) but expose a simple uncontrolled API for simple forms. This is a form of composability: let callers choose their complexity level.
The trap: picking one pattern and enforcing it everywhere. Controlled works best for complex, data-bound UIs; uncontrolled works best for one-off, simple inputs. A good Design System clarifies which is the default and when to break it.
Related: Component API · Component states · Composability · Component