Text input
A single-line field for a short answer the reader types: a name, an email, a search term. When the answer comes from a known list, use Select; when it runs past a line, use Text area.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<TextInput
label="Email address"
type="email"
helpText="We only use it to send the receipt."
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/text-input.jsonimport { TextInput } from '@/components/ui/text-input'
import type { FieldSize, FieldState } from '@/components/ui/text-input'Anatomy
Slots come from the contract. The label and supporting text are part of the control, not a wrapper around it, so there is no way to render the field without a name.
- ValueRequiredNone beyond the value itself.
- Leading iconOptional
- Trailing iconOptional
- LabelRequiredBuilt into the control, not supplied by a wrapper. The kit ships no standalone label component and no field wrapper; it makes label text a property of the control itself.
- Supporting textOptionalHelp text, or error text. The kit calls this Helper / Error text and builds it into the control the same way.
Variants
Size changes the field’s height only: the value is Body/3 at every size, and the label and supporting text stay at 12/16. Layout gives the kit’s three shapes: Fixed, Inline and Fluid. showCount adds the kit’s character count.
States
Focus is forced here with the declarations :focus-within carries; it is never a prop. There is no Hover row because the field has no hover style. Error and Warning come from passing errorText or warningText and draw the kit’s status icon; Invalid is the error ring without a message. Read-only is the native attribute.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| type | 'text' | 'email' | 'password' | 'number' | … | — | password is a plain masked field; for one the reader may need to check, use Password input (#284). number is a bare numeric field; for a value that moves in steps, use Number input (#285). |
| size | 'sm' | 'md' | 'lg' | — | |
| state | 'default' | 'focus' | 'disabled' | 'error' | 'invalid' | 'warning' | — | |
| layout | 'fixed' | 'inline' | 'fluid' | — | The kit's Style axis and its second set. Fixed puts the label above the field; Inline beside it, with the message to the field's right; Fluid is the kit's Text input - Fluid, one 64px box with the label inside. Inline is built at the Fixed heights (32/40/48); the kit's Inline Medium at 48 reads as a Carbon leftover. |
| label | — | — | Required. There is no shape in which this control exists unlabelled, and no wrapper left to supply one. |
| hideLabel | boolean | — | A field the kit draws bare (Slider's value inputs, #269). The label stays the accessible name, hidden from view; it is still required. Fixed layout only. |
| helpText | — | — | Supporting copy. Suppressed while errorText or warningText is present. |
| warningText | — | — | Its presence resolves the warning state, the same way errorText resolves error, and an error outranks it. The edge stays the rest rule; the warning is carried by the status icon and the message. |
| showCount | — | — | The kit's Show count. A running "n/max" at the right of the label row, driven by maxLength. |
| errorText | — | — | Its presence resolves the error state, so error text and error styling cannot be shown apart. This was Field's guarantee and it survives Field. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
elevationThe field's fill, elevation-01, the kit's Field/field-01. Select's hover rung, elevation-02, comes through the same inheritance.outlineThe bottom rule at rest, through its strong step (outline-strong), which is also the placeholder colour; read-only quietens it to the subtle step. A rule on the bottom only, never a box.primaryThe focus ring, through the family's focus step: 2px inside all four sides.dangerThe error ring, 2px inside, the error text, and the error status icon's triangle.warningThe warning status icon's triangle.on-warningThe "!" on the warning status icon.backgroundThe "!" on the error status icon, which the kit cuts out of the triangle to show what is behind it.on-surfaceValue text.textThe value at Body/3 at every size; size changes the height, never the type.spacingField padding and height steps.radiusField corner. Inherited by Select and Text area.on-surface-variantLabel and helper text, which the kit binds to onSurfaceVariant rather than onSurface.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Write the label as the thing being asked for, and keep it short enough to stay on one line.
- Pass errorText to show an error. The red ring and the message come from the same value, so they arrive together and leave together.
- Set type to match the answer (email, password, number), so a phone shows the right keyboard.
- Use the trailing slot for an action on the value, like clearing it, and give that button its own accessible name.
Don’t
- Use the placeholder as the label. It disappears as soon as someone types, and the contract requires a visible label.
- Mark a field invalid with nothing nearby to say why. A red ring says something is wrong, not what.
- Wrap the control in a label or field of your own. The label is built in, and a second one gets read twice.
- Give focus a colour of its own. The focus ring is the primary family’s focus step, the same ring Button draws.
Accessibility
What the component does for you, and what it leaves to you.
- Labels
- The label is a real label element tied to the input by id. An id is generated when you do not pass one, so the two always associate.
- Supporting text
- Help, warning or error text is linked through aria-describedby. Error text also carries role="alert", so it is announced when it appears. The status icon is decorative; the message carries the meaning.
- Validity
- Error and Invalid both set aria-invalid. The required asterisk is hidden from assistive tech; the native required attribute is what gets read.
- Focus
- Focus is the browser’s own, drawn as the kit’s 2px ring inside the field through :focus-within. Nothing sets it by hand, so the ring and the real focus cannot disagree.
- Disabled
- Disabled uses the native attribute, so the field leaves the tab order and is not submitted with the form.
Figma parity
The kit's Text input page ships 81 variants across 2 sets. The code exposes 10 props. This table is where those two facts are reconciled instead of quietly diverging.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Style | Fixed · Inline | layout | Both. Inline puts the label beside the field and the message to its right, built at the Fixed heights; the kit’s Inline Medium at 48 reads as a Carbon leftover. |
| Size | Small · Medium · Large | size | One to one: 32, 40 and 48, with the value Body/3 at every size. |
| State | Enabled · Focus · Error · Warning · Disabled · Read-only · Skeleton | state | Error and Warning follow errorText and warningText and draw the kit’s status icon. Disabled maps to the prop and the native attribute; Read-only is the native readOnly. Focus is :focus-within, per governance rule 7. Skeleton has no counterpart. Invalid is the code’s own value. |
| Show count | Boolean | showCount | The kit’s “n/max” at the right of the label row, driven by maxLength. |
| Text filled | False · True | — | Runtime state: whether the field has a value. The kit draws it because a Figma frame cannot be typed into. |
| Fluid (set) | Enabled → Read-only | layout="fluid" | The kit’s second public set: one 64px box with the label inside, a plain outline rule on the bottom. |
