TextField
Single- or multi-line text input. The label, the description and the error are one component, wired together by Base UI's Field.
import { TextField } from 'neba';
<TextField label="Email" value={email} onChange={(event) => setEmail(event.target.value)} />;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the surface. Even solid is not flooded with colour — a field holds user data |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height and type scale. The same heights as Button, so a row's baseline holds |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. The surface is white, so it reaches the edge, the focus ring and the caret |
| densityshared | 'default' | 'compact' | 'default' | Padding only — never the height, never the type scale |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. A field is a well, not a surface that floats — rarely raised |
| label | ReactNode | — | Label above the control, wired to it by Base UI's Field |
| description | ReactNode | — | Helper text below the control |
| error | ReactNode | — | Error message below the control. Its presence also turns the field invalid |
| invalid | boolean | !!error | Forces the invalid state without a message, for when a form library owns validity |
| multiline | boolean | false | Renders a textarea instead of an input. Every other axis stays identical |
| rows | number | 3 | Visible rows in multiline mode |
| resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | Which way the user may drag it. Horizontal breaks a form's column |
| startIcon | ReactNode | — | Content before the control |
| endIcon | ReactNode | — | Content after the control |
| loading | boolean | false | Spinner in place of endIcon. Typing is deliberately still allowed |
| readOnly | boolean | false | Read-only. Selecting and copying still work |
| disabled | boolean | false | Unavailable |
| fullWidth | boolean | false | Stretches to the width of the container |
Every native <input> attribute passes straight through. color and size are omitted because they collide with the ones above, and onChange is widened so the same handler types against a <textarea> in multiline mode.
Examples
Variants
Even solid is not flooded with the accent colour. What a field holds is user data, and a caret, a text selection and a placeholder all have to stay legible on top of it. The colour family shows up in the edge, the focus ring and the caret instead.
The sheet itself is white, and going outline → solid → hover → focus makes it more opaque, not more coloured. Colour has the whole rule.
Sizes
The same heights as Button, so a field and a button in one row share a baseline.
Multiline
multiline renders a <textarea> and changes nothing else. rows={1} is exactly as tall as the single-line field. Horizontal resizing breaks a form's column, so only the vertical axis is on by default.
Icons and progress
loading puts a spinner in the endIcon slot and marks the field busy, but typing is deliberately still allowed — a field is usually loading because of what was typed into it.
States
Give error any content and the whole field re-points at the danger family — edge, focus ring, caret and message all turn over together. To mark it invalid without a message, pass invalid directly.
Controlled
value and onChange are the native ones.
Accessibility
- Base UI's Field connects the label, description and error to the control with
idandaria-describedby. - There is no floating-label variant on purpose: floating labels need a
transform, and controls in this library never transform. - The focus ring belongs to the shell rather than the control inside it, so it traces the acrylic edge.
- Clicking the shell's own padding puts the caret in the field, the way a native
<input>behaves.