Field

The standard form row: label, control, and helper or error text — wrap any input. It is also where the system answers a wrong value: the shake is armed by the transition into an error, never by the presence of one — a page that arrives with server-rendered errors already on it would otherwise jitter every invalid row at once on load, which reads as a rendering fault rather than as an answer to something you just did. Feedback has to stay attached to the action that caused it. Getting the same field wrong twice still re-arms it, because the error clears while you type, so the second failure is a real transition rather than a value that was already sitting there.

tsx
import { Field, Input, Button } from "@pestoui/core"

Label, hint & error

Shown on the agent card.

That workspace is already taken.

Validation error

Submit while the field is empty, or with something that isn't an address. The row runs six decaying stops over 450ms — the decay is the whole difference between a shake and a vibrate — and the message underneath enters on its own clock: it starts 4px high, 4px out of focus and fully transparent, then resolves all three to zero. Fix the value and submit again to watch it pass. The hint that replaces the error enters the same way, because the message line is keyed on its text, so a swap mounts a fresh node — and the enter is a @starting-style transition, which only a fresh node can fire.

One address per workspace.

The shake is gated, not automatic

One error, reached two ways. Set it on a row that is already on screen and the row shakes, because you are watching a state change happen. Remount the row with the error already present — which is what a server-rendered form does — and it stays perfectly still, because nothing changed while you were looking. The gate is a flag raised only when the error differs from the previous render, then dropped again on animationend so the next failure restarts the keyframes instead of inheriting a class that is already applied.

Lowercase letters, numbers and dashes.

Focus animates colour, not geometry

The input carries its focus ring at rest — full width, zero alpha — so focusing it only moves the ring's colour. Nothing grows. The second field is the same input with that resting ring taken away: focus now has to inflate three pixels of new geometry, and even eased, it reads as the control changing size rather than lighting up. Colour, background, border and shadow all travel together on one 150ms curve, which is also why the invalid state crossfades out of focus instead of snapping.

The default. Focus moves the ring's colour and nothing else.

Resting ring removed. Focus inflates new geometry.

Props

PropTypeDefaultDescription
labelstring—Field label text, rendered through Label so it inherits the disabled dimming of the control it names.
htmlForstring—Ties the label to the control’s id. The message id and aria-invalid are wired onto the child for you, so the shake is something a screen reader can follow.
hintstring—Helper text below the control, shown while there is no error. Hint and error share one line, keyed on the text, so swapping between them mounts a fresh node and each enters rather than retyping in place.
errorstring—Error text — replaces the hint and turns red, and is also the trigger. Going from no error to an error, or from one message to a different one, shakes the row and enters the text. Arriving already set does neither.
optionalbooleanfalseAppends a muted “Optional” marker to the label.

Pesto UI · 117 documented components