Controlled vs uncontrolled

Select works in both controlled and uncontrolled modes:

typescript
1// Uncontrolled — Select manages its own state
2<Select label="Color" defaultValue="blue">
3 <Select.Option value="red">Red</Select.Option>
4 <Select.Option value="blue">Blue</Select.Option>
5</Select>
6
7// Controlled — you manage state externally
8const [value, setValue] = useState('blue')
9
10<Select label="Color" value={value} onChange={setValue}>
11 <Select.Option value="red">Red</Select.Option>
12 <Select.Option value="blue">Blue</Select.Option>
13</Select>

Uncontrolled for simple cases, controlled for complex state management

Overview

React components can be controlled (parent owns the state) or uncontrolled (component owns its own state). Flint UI supports both patterns for every interactive component.

Uncontrolled (Default)

The simplest approach. The component manages its own state internally. You read the value when you need it (e.g., on form submit).

typescript
1import { Select } from '@flint-ui/core'
2
3function UncontrolledExample() {
4 return (
5 <form onSubmit={(e) => {
6 const data = new FormData(e.currentTarget)
7 console.log(data.get('color'))
8 }}>
9 <Select name="color" defaultValue="blue">
10 <Select.Option value="red">Red</Select.Option>
11 <Select.Option value="blue">Blue</Select.Option>
12 <Select.Option value="green">Green</Select.Option>
13 </Select>
14 <button type="submit">Submit</button>
15 </form>
16 )
17}

Controlled

The parent component owns the state. Use this when you need to react to changes immediately, validate on change, or sync with other UI.

typescript
1import { useState } from 'react'
2import { Select } from '@flint-ui/core'
3
4function ControlledExample() {
5 const [color, setColor] = useState('blue')
6
7 return (
8 <>
9 <Select value={color} onChange={setColor}>
10 <Select.Option value="red">Red</Select.Option>
11 <Select.Option value="blue">Blue</Select.Option>
12 <Select.Option value="green">Green</Select.Option>
13 </Select>
14 <p>Selected: {color}</p>
15 </>
16 )
17}

When to Use Which

PatternUse When
UncontrolledSimple forms, no real-time validation needed
ControlledDependent fields, live preview, complex validation

Flint UI Convention

Every interactive Flint UI component accepts both patterns:

  • defaultValue / defaultOpen / defaultChecked → uncontrolled
  • value / open / checked + onChange / onOpenChange → controlled

If you provide both value and defaultValue, the controlled prop (value) takes precedence.