VI Controls

Star on GitHubFeedback
VI ControlsNative HTMLot-field

Field

No box, only a baseline. Your words arrive in italic, focus draws the accent from where you touched, and an error hangs from the line as a callout. Or your words are overprinted on a heavy label, or signed on a form's line.

Report an issue with Field

Example

Check the address. It needs a domain after the @, like studio.com.

Ruled like paper. The lines scroll with what you write.

Your name as it should appear on the agreement.

Rest
Focus
Filled
Error

Add the ending.

Disabled
overprint, rest
overprint, focus
overprint, filled
signature, rest
signature, focus
signature, signed
signature, error

Sign in full.

Install

Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.

npx shadcn@latest add https://thedirectors.agency/ui/r/field.json

Usage

field-example.tsx
import { Field, Input, Textarea } from "@/components/ui/field"export default function Example() {return (<div className="grid w-full max-w-md gap-10"><Field label="Your name" count maxLength={40}><Input autoComplete="name" placeholder="As you'd like to be addressed" /></Field><Field label="Email" error="Check the address. It needs a domain after the @, like studio.com."><Input type="email" defaultValue="hello@studio" autoComplete="email" /></Field><Field label="Notes" count maxLength={400} hint="Ruled like paper. The lines scroll with what you write."><Textarea rows={4} placeholder="What should we know first?" /></Field><Field variant="overprint" label="Working title"><Input autoComplete="off" /></Field><Field variant="signature" label="Signed, in full" hint="Your name as it should appear on the agreement."><Input autoComplete="name" /></Field></div>)}

Contract

  • Underneath: native input and textarea, plus a hook for the counter. The count and filled state follow controlled values, child changes and native form resets as well as input events.
  • Anatomy: .ot-field holding a .ot-label, an optional .ot-field-count, then the input or textarea, then an optional .ot-field-hint or .ot-field-error. The counter stays at the inline end and reads count / limit, isolated left to right even on an RTL page.
  • No box, only a baseline. Focus draws the accent line outward from where the pointer touched it (script sets --o, a percentage) or from the left for the keyboard, and shows the counter. The placeholder steps back to half strength on focus.
  • Yours: the typed value, in the italic. The placeholder stays in the voice, in pencil.
  • Textarea: ruled like paper, with lines every --lh (2.25rem) that scroll with the text.
  • A search input (type="search") draws no native clear mark, whose blue × belongs to no scheme; Escape clears it.
  • States: rest, focus, filled, error (aria-invalid="true" turns both lines crimson; the message in .ot-field-error is linked with aria-describedby), disabled (dotted baseline).
  • The error is a callout, after Weingart: a crimson hairline pill hung from the baseline by a leader line, with a dot where it meets the line. A textarea's ruled lines turn crimson too. It arrives in order: the dot lands, the leader drops, then the pill and its words.
  • Error copy says what to fix: "Check the address. It needs a domain after the @, like studio.com."
  • Variant overprint (after "It has to be design."): the label is set in the voice as a heavy condensed word at --ot-f, and your italic is printed over its lower half, knocked out of it by a paper outline (-webkit-text-stroke in --ot-paper, painted under the fill). There's no placeholder; the label is the prompt. Arriving, the word draws in its width (Inhale) and your words and caret take the accent, so the baseline stays a hairline.
  • Variant signature (the printed form's signature line): a cross (.ot-field-mark) stands at the start of the line and the label becomes a caption under it. Your words are set larger, at --ot-mf. Arriving, the + turns a quarter to the × of "sign here" and inks; once written (data-filled) it stays × in pencil. The caption holds the start under the line, so the error callout hangs from the end.

Props

Props of Field
PropTypeDefaultWhat it does
variant"line" | "overprint" | "signature""line"How the label and your words share the line. line: a small label over a baseline. overprint: the label is set as a heavy condensed word and your italic is printed over it, knocked out of it by a paper outline, as in "It has to be design."; arriving, the word draws in its width and your words take the accent. signature: a cross marks where to write and the label is a caption under the line, as on a printed form; arriving, the + turns to the × of "sign here" and inks, and once you've written it steps back to pencil.
labelReactNodeSmall sans label, wired to the control with htmlFor.
hintReactNodeA quiet line under the baseline. Linked with aria-describedby.
errorReactNodeWhat to fix, in plain words. The baseline turns crimson, the control gets aria-invalid, and the message hangs from the line on a leader. A Form fills this in for you.
countbooleanfalseShow how much is typed, such as 12 / 40, while the field has focus.
maxLengthnumberPassed to the control, and the top of the count.
Input / TextareanativeReal controls with every native prop. Textarea is ruled like paper. Both also work outside a Field.
FieldErrorcomponentThe callout on its own, for an error you place yourself.
idstringgeneratedThe control's ID, used by the label and hint/error associations. Input and Textarea inherit it; a custom control must use the same ID.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
Field errorWeingart calloutsPill on a leader line from the baseline
Overprint field"It has to be design."Your italic printed over a heavy condensed label, knocked out in paper
Signature fieldThe printed form's signature lineA cross where you write; the label a caption under the line

How it moves

How it moves
ComponentArticulationWhat moves
FieldFrom the touchThe accent line grows out from where you touched it
Field errorArpeggioDot, leader, pill, in that order
Overprint fieldInhaleArriving, the label draws in its width and your words take the accent
Signature fieldSpiccatoArriving, the + turns to × and inks; written, it steps back to pencil