Skip to main content
Graphite UI

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.

Contract 2.3.2Kit · 2 sets · 81 variantsOpen in Figma

Live preview

Rendered by the component itself from the same generated tokens as the rest of the site.

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.json
import { 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.

Supporting text
  1. ValueRequiredNone beyond the value itself.
  2. Leading iconOptional
  3. Trailing iconOptional
  4. 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.
  5. 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.

Size: Small
Size: Medium
Size: Large
Layout: Inline
Helper text
Layout: Fluid
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.

Enabled
Helper text
Focus
Helper text
Disabled
Helper text
Error
Error message
Warning
Warning message
Read-only
Helper text
Invalid
Helper text

API reference

Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.

Text input props
PropTypeDefaultNotes
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.
hideLabelboolean—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 axes and how they map to code
Kit axisValuesCodeHow it maps
StyleFixed · InlinelayoutBoth. 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.
SizeSmall · Medium · LargesizeOne to one: 32, 40 and 48, with the value Body/3 at every size.
StateEnabled · Focus · Error · Warning · Disabled · Read-only · SkeletonstateError 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 countBooleanshowCountThe kit’s “n/max” at the right of the label row, driven by maxLength.
Text filledFalse · 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-onlylayout="fluid"The kit’s second public set: one 64px box with the label inside, a plain outline rule on the bottom.