Input
- Component overview: Standard text input for forms and single-line text entry. Supports prefix/suffix slots.
- Size baseline: Height 40px, horizontal padding 16px, border radius 5px.
- Implementation note:
ui/inputretains the input skeleton; the design layer owns the border, border radius, states, and slot layout. - Figma spec
Basic Usage
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <Input placeholder="Default input" /> <Input placeholder="With initial value" defaultValue="Hello world" /> </div>
States
4 visual states: Default / Focused / Error / Disabled.
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Default</span> <Input placeholder="Default state" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Error</span> <Input error placeholder="Error state" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled (empty)</span> <Input disabled placeholder="Disabled" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled (with value)</span> <Input disabled defaultValue="Read only" /> </div> </div>
| State | Border | Background | Note |
|---|---|---|---|
| Default | Separators/Emphasized#CCCCCC | transparent | — |
| Focused | Labels/Primary#000000 | transparent | Click to preview |
| Error | Status/Destructive#FF503F | transparent | error prop |
| Disabled | Separators/Emphasized#CCCCCC | Grays/Gray-1#EBEBEB | disabled prop |
Main Matrix
The current spec acceptance is based on State × Filled × Status. The doc site prioritizes the core combinations matching the current implementation.
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, maxWidth: 760 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Default</span> <Input placeholder="Select content" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Default</span> <Input defaultValue="Filled value" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Focused / Empty / Default</span> <Input className="border-[var(--Labels-Primary)]" placeholder="Focused state" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Focused / Filled / Default</span> <Input className="border-[var(--Labels-Primary)]" defaultValue="Focused value" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Error</span> <Input error placeholder="Error state" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Error</span> <Input error defaultValue="Error value" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Empty / Default</span> <Input disabled placeholder="Disabled empty" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Filled / Default</span> <Input disabled defaultValue="Disabled value" /> </div> </div>
Prefix and Suffix
Use the prefix and suffix props to render slot content (icons, buttons, etc.).
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <Input prefix={<Search size={16} />} placeholder="Search..." /> <Input suffix={<Mail size={16} />} placeholder="Email" /> <Input prefix={<Search size={16} />} suffix={<Mail size={16} />} placeholder="Both slots" /> <Input prefix={<Search size={16} />} error placeholder="Error + prefix" /> <Input prefix={<Search size={16} />} disabled placeholder="Disabled + prefix" /> </div>
Clearable
Set allowClear to render a clear (×) button. Following the Figma spec, the button only appears when the input is focused and has content (Filled=True + Focused); it hides on blur or when empty. Works in both controlled and uncontrolled modes.
function ClearableDemo() { const [value, setValue] = useState('Hello world') return ( <div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <Input allowClear defaultValue="Click to focus, then clear" /> <Input allowClear value={value} onChange={(e) => setValue(e.target.value)} onClear={() => setValue('')} placeholder="Controlled clearable" /> </div> ) }
Size Spec
| Dimension | Value |
|---|---|
| Height | 40px |
| Horizontal padding | 16px |
| Prefix/content/suffix gap | 8px |
| Border radius | 5px |
| Font | Body/Regular (14px, line-height 22) |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | - | Placeholder text |
value | string | - | Controlled value |
defaultValue | string | - | Initial value (uncontrolled) |
type | string | 'text' | Input type |
error | boolean | false | Error state — border uses Status/Destructive |
disabled | boolean | false | Disabled state |
allowClear | boolean | false | Show a clear button when focused with content |
prefix | ReactNode | - | Prefix slot (e.g. icon) |
suffix | ReactNode | - | Suffix slot (e.g. button) |
onChange | ChangeEventHandler | - | Change callback |
onClear | () => void | - | Clear button callback |
clearButtonAriaLabel | string | '清空输入' | Clear button accessible name; override for localization |
aria-label | string | - | Accessibility label |
Input.Search
- Component overview: Search input with a search icon, clear button, and two style variants: outline and filled.
- Figma spec
- Implementation note: The search icon, input area, and clear button are laid out by the design layer; the underlying input structure reuses
ui/input.
Variants
2 style variants × 2 sizes = 4 combinations.
<div style={{ display: 'flex', flexDirection: 'column', gap: 24, maxWidth: 360 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Outline / Default (40px)</span> <Input.Search placeholder="Search..." /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Outline / Compact (32px)</span> <Input.Search size="compact" placeholder="Search..." /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Filled / Default (40px)</span> <Input.Search variant="filled" placeholder="Search..." /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Filled / Compact (32px)</span> <Input.Search size="compact" variant="filled" placeholder="Search..." /> </div> </div>
| Style | Size | Height | Description |
|---|---|---|---|
| Outline | Default | 40px | With border, highlighted on focus |
| Outline | Compact | 32px | Small with border |
| Filled | Default | 40px | Gray background, no border |
| Filled | Compact | 32px | Small with gray background |
Controlled + Clear
A clear button appears when the field is focused and has content (aligned with the Figma Focused + Filled state).
function SearchDemo() { const [value, setValue] = useState('') return ( <div style={{ maxWidth: 360 }}> <Input.Search placeholder="Type to search..." value={value} onChange={(e) => setValue(e.target.value)} onClear={() => setValue('')} /> {value && <p style={{ fontSize: 13, color: '#666', marginTop: 8 }}>Search: {value}</p>} </div> ) }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | - | Placeholder text |
value | string | - | Controlled value |
defaultValue | string | - | Initial value (uncontrolled) |
size | 'default' | 'compact' | 'default' | Size: default=40px, compact=32px |
variant | 'outline' | 'filled' | 'outline' | Style: outline (with border) / filled (gray background) |
onChange | ChangeEventHandler | - | Change callback |
onClear | () => void | - | Clear button callback |
clearButtonAriaLabel | string | '清空搜索' | Clear button accessible name; override for localization |
Input.Password
- Component overview: Password input with a built-in visibility toggle button. Click the eye icon to switch between masked and plain text.
- Size baseline: Same as Input — height 40px, horizontal padding 16px, border radius 5px.
- Implementation note: Composes
Inputwith a suffix eye icon toggle. The suffix slot is occupied by the toggle button. - Figma spec
Basic Usage
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <Input.Password placeholder="Password" /> <Input.Password placeholder="Password" defaultValue="Plaud-2026" /> </div>
States
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Default</span> <Input.Password placeholder="Password" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Error</span> <Input.Password error placeholder="Invalid password" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled</span> <Input.Password disabled placeholder="Password" /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled (with value)</span> <Input.Password disabled defaultValue="secret123" /> </div> </div>
Controlled + Visibility Callback
function PasswordDemo() { const [value, setValue] = useState('') const [visible, setVisible] = useState(false) return ( <div style={{ maxWidth: 360 }}> <Input.Password placeholder="Enter password" value={value} onChange={(e) => setValue(e.target.value)} onVisibilityChange={setVisible} /> <p style={{ fontSize: 13, color: '#666', marginTop: 8 }}> Visible: {visible ? 'Yes' : 'No'} | Length: {value.length} </p> </div> ) }
Namespace Usage
Input.Password and Input.Search are available as namespace sub-components.
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 360 }}> <Input.Password placeholder="Input.Password" /> <Input.Search placeholder="Input.Search" /> </div>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | - | Placeholder text |
value | string | - | Controlled value |
defaultValue | string | - | Initial value (uncontrolled) |
error | boolean | false | Error state — border uses Status/Destructive |
disabled | boolean | false | Disabled state |
prefix | ReactNode | - | Prefix slot (e.g. lock icon) |
onVisibilityChange | (visible: boolean) => void | - | Visibility toggle callback |
onChange | ChangeEventHandler | - | Change callback |
aria-label | string | - | Accessibility label |