Text field

There are two layers here. input() and textarea() are the bare controls; textField() and textareaField() wrap one in a label, help text and an error message, and connect them with for and aria-describedby. Reach for the second unless you are building the layout yourself.

Basic text field

textField({ label: 'Name', name: 'name', placeholder: 'Ada Lovelace' })

Help text

Help text is linked with aria-describedby, so a screen reader reads it as part of the field rather than as loose text after it.

We only use it to send build failures.
textField({
  label: 'Email',
  name: 'email',
  type: 'email',
  help: 'We only use it to send build failures.',
})

Required and error

An error marks the field invalid, colours the border, sets aria-invalid and points aria-describedby at the message — one prop, all four.

That is not a URL.
stack({ gap: 'lg' },
  textField({ label: 'Project', name: 'project', required: true, value: '' }),
  textField({
    label: 'Site',
    name: 'site',
    error: 'That is not a URL.',
    value: 'sitelo dot dev',
  }),
)

Sizes

stack({ gap: 'md' },
  textField({ label: 'Small', name: 'small', size: 'sm', placeholder: 'sm' }),
  textField({ label: 'Medium', name: 'medium', size: 'md', placeholder: 'md' }),
  textField({ label: 'Large', name: 'large', size: 'lg', placeholder: 'lg' }),
)

Adornments

A prefix or suffix attached to the control itself, for units and fixed fragments of a value.

https://
seconds
stack({ gap: 'md' },
  textField({ label: 'Site', name: 'url', startAdornment: 'https://', placeholder: 'example.com' }),
  textField({ label: 'Build timeout', name: 'timeout', endAdornment: 'seconds', value: '30' }),
)

Disabled and read-only

stack({ gap: 'md' },
  textField({ label: 'Disabled', name: 'disabled', value: 'Cannot edit', disabled: true }),
  textField({ label: 'Read-only', name: 'readonly', value: 'dist/', readonly: true }),
)

Multiline

textareaField() is the same field around a <textarea>. Its value is element content rather than an attribute, which the component handles for you.

Shown in search results and social cards.
textareaField({
  label: 'Description',
  name: 'description',
  rows: 4,
  help: 'Shown in search results and social cards.',
  value: 'Zero-config static site generation powered by Vite.',
})

In a form

card(
  cardBody(
    stack({ gap: 'md' },
      textField({ label: 'Name', name: 'contact-name', required: true }),
      textField({ label: 'Email', name: 'contact-email', type: 'email', required: true }),
      textareaField({ label: 'Message', name: 'message', rows: 3 }),
    ),
  ),
  cardFooter({ divided: true, style: 'justify-content: flex-end' },
    button({ variant: 'ghost', color: 'neutral' }, 'Cancel'),
    button({ type: 'submit' }, 'Send'),
  ),
)

Building it yourself

field() is the wrapper on its own — it takes any control as children, so you can put two inputs on one row, or a control this library does not have, under the same label and error treatment.

One label cannot name two controls, so each input needs its own accessible name here. That is what the aria-labels are doing: the visible label names the pair, and each input says which end it is.

Both ends are inclusive.
field({ label: 'Date range', help: 'Both ends are inclusive.' },
  stack({ direction: 'row', gap: 'sm' },
    input({ type: 'date', name: 'from', 'aria-label': 'From' }),
    input({ type: 'date', name: 'to', 'aria-label': 'To' }),
  ),
)

Props

textField and textareaField

PropTypeDefaultDescription
labelChildThe field label. Also derives the control id when there is no name.
namestringForm field name; the id is derived from it.
helpChildHint under the control, linked with aria-describedby.
errorChild | falseError message. Also sets aria-invalid on the control.
requiredbooleanfalseMarks the label and the control.
size'sm' | 'md' | 'lg''md'Control height and text size.
typestring'text'Any input type. textField only.
startAdornmentChildPrefix attached to the control. textField only.
endAdornmentChildSuffix attached to the control. textField only.
valuestring | numberInitial value.
fieldClassstringClass for the wrapper rather than the control.

Ids are derived from name — or from label when there is no name — rather than from a counter, so the same page renders the same HTML on every build. Pass id to override.

field

PropTypeDefaultDescription
labelChildThe label text.
helpChildHint under the control.
errorChild | falseError message; also adds the invalid state to the wrapper.
requiredbooleanfalseAdds the required marker to the label.
forstringId of the control being labelled.