Pro

Text field

Labeled input that validates when you leave it and clears the error as soon as the value is fixed. The error takes the hint’s line, so nothing shifts.

Send the invitation

The invitation and a link to RSVP go here.

Printed on the invitation.

Enter a deposit up to the $9,850 total

Set automatically.

Protect the guest page

Guests enter this to see the menu and RSVP.

Needed to change who can see the event.

Installation

With UI Polish connected, ask your agent:

Add the UI Polish text field to this project.

Or with the shadcn CLI:

npx shadcn@latest add https://uipolish.com/r/text-field.json
Brings inIcons

A labeled single-line input with a hint and an error. It checks the value when you leave the field, and once an error shows it clears the moment the value is fixed. The hint and the error share one reserved line, so nothing below the field moves. A field with no hint, validate or error reserves nothing, so a stack of plain fields keeps the form's own gap.

When to use it

  • Any single-line text, email, number, password or URL input that needs a visible label.
  • Forms where errors should be fixable in place, field by field.

When not to

  • Multi-line text: use a textarea with the same label and message slot.
  • Picking from known options: use a select or combobox.
  • Search boxes in a toolbar: they usually need no visible label, an icon, and a clear button, so use a search field built for that.

States

StateWhat you seeHow
RestHairline edge in --ui-border-strong, hint in muted text below
HoverEdge one step darker, eased in and out@media (hover: hover), skipped when focused, invalid or disabled
FocusEdge thickens to 1px and darkens to --ui-field-focus, the instant focus lands; leaving eases back:focus (fields show focus on click too)
InvalidEdge eases to danger; the hint fades out and the error, with its icon, fades in in the same placearia-invalid, set on blur by validate or by error
Invalid + focus1px danger edge
Fixed while typingEdge eases from danger back to the focus color; the error fades out and the hint fades back invalidate re-runs on each keystroke once an error showed
New error over an old oneThe new message fades in where the old one was
Disabled50% opacity, default cursor, no transitions in or outnative disabled
Password, hiddenDots, an eye button inside the end of the fieldtype="password"
Password, shownPlain text, the eye gets a slash, the button turns to the text colorthe button's aria-pressed="true"

One reserved slot. The hint and the error sit in the same grid cell and trade places with a crossfade. The cell is as tall as the taller of the two, and the last error stays in it (faded out and visibility: hidden) after it's fixed, so the field's height never changes when the error comes and goes. Only the line showing is visible, so only it is in the description and the live region.

When it validates. Not while the user is typing a first attempt. On blur it runs validate. If that returns a message, the field re-checks on every keystroke after that and clears the error as soon as the value passes. Errors from the server go in error and win over validate.

What an error says. How to fix it: "Enter an email like name@example.com", not "Invalid input". "Use at least 8 characters", not "Too short".

Passwords. type="password" adds a reveal button inside the field. The input reserves the button's room whether or not anything is shown, so the field never changes width or height and the text never moves. The button is 32px, centered in the field's height, and its radius is the field's minus the gap around it. A pointer press doesn't take focus from the input, so the caret and the on-screen keyboard stay put, and the caret is put back where it was after the type changes. From the keyboard the button is the next Tab stop after the input; Space or Enter toggles it and focus stays on the button, so it can be pressed again.

Motion

WhatDurationEasing
Edge on hover, and back from focus--ui-dur-instant (120ms)--ui-ease-color
Edge on focus0ms, never animatedn/a
Edge to danger and back--ui-dur-fast (160ms), also while focused--ui-ease-color
Leaving message (hint or error) fades out--ui-dur-instant (120ms)--ui-ease-color
Arriving message fades in, 75ms after--ui-dur-fast (160ms)--ui-ease-enter
A new error replacing a shown one--ui-dur-fast (160ms) fade--ui-ease-enter
Reveal: slash draws across the eye (Icon eye → eye-off)see the icon set's guide
Reveal button hover color--ui-dur-instant (120ms)--ui-ease-color
Reveal button press release--ui-dur-snap (240ms)--ui-ease-snap

Nothing in the field moves: the edge only changes color and width, and the messages only fade. The edge is drawn from two registered custom properties, one for the neutral color and one for how far it's mixed toward danger, so focus can land instantly while validity still eases. Under prefers-reduced-motion: reduce the colors and fades stay (they're short feedback, not motion), a replaced error swaps without fading, and the reveal button doesn't scale.

Layout

Stack fields with a 16px gap (--ui-space-4). Inside a field the label, input and message sit 6px apart, so each field reads as one group and the reserved message line never crowds the next label.

Accessibility

  • The label is a real <label for>, so clicking it focuses the input and screen readers announce it.
  • The hint or error is linked with aria-describedby. Invalid fields get aria-invalid="true".
  • The message slot is aria-live="polite", so an error that appears on blur is read out without stealing focus.
  • Inputs are 16px on touch devices so iOS doesn't zoom the page on focus.
  • The reveal button is a toggle: aria-label="Show password", aria-pressed, aria-controls pointing at the input. The label stays the same and the pressed state carries the change, so a screen reader says "Show password, toggle button, pressed" rather than a label that contradicts its state. Its hit area grows to 40px on touch.
  • Password fields turn off spellcheck and autocorrect, so a shown password is never underlined or sent to a spelling service.

Before / after

Before: the error renders only when there is one, so the form jumps down, and it shows on the first keystroke.

TSX
<label>Email</label><input value={email} onChange={(e) => setEmail(e.target.value)} />{!isEmail(email) && <p className="error">Invalid email</p>}

After: checked on blur, cleared as soon as it's fixed, no jump, linked to the input for screen readers.

TSX
<TextField  label="Email"  type="email"  autoComplete="email"  hint="We'll send the invitation here."  validate={(v) => (isEmail(v) ? null : "Enter an email like name@example.com")}/>

A password, with a reveal button that doesn't move anything:

TSX
<TextField  label="Guest page password"  type="password"  autoComplete="new-password"  hint="Guests enter this to see the menu and RSVP."  validate={(v) => (v.length >= 12 ? null : "Use at least 12 characters")}/>