elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Forms / Field

Field

A label, a control, a description and an error wired together, with native validation that never moves the layout.

Input, textarea, select

The field wires the label, the description and the error to the control by id.

Choose a unique username for your account.

Must be at least 8 characters long.

Share your thoughts about our service.

Select your department or area of work.

Code
<Field label="Username" description="Choose a unique username for your account.">
  <Input value={account.username} placeholder="Max Leiter" />
</Field>
<Field label="Password" description="Must be at least 8 characters long.">
  <Input type="password" value={account.password} attrs={{ minlength: 8 }} />
</Field>

Usage

import { Field, FieldLabel, FieldDescription, FieldError, FieldContent, FieldTitle, FieldSet, FieldLegend, FieldGroup, FieldSeparator } from "@elements/ui/field";

<Field label="Email" description="We never share it." required>
  <Input type="email" value={form.email} />
</Field>

<Field label="Username" validate={checkUsername} validationMode="onBlur">
  <Input value={form.username} attrs={{ minlength: 3 }} />
  <div slot="error">
    <FieldError match="tooShort">At least 3 characters.</FieldError>
  </div>
</Field>

<Field orientation="horizontal" label="Email me updates">
  <Checkbox checked={form.updates} />
</Field>

Examples

Validation

Native constraints plus validate(). Errors take the description's line, so nothing moves.

Enter your email.
That is not an email address.

Try admin.

Whole years.

submitted: 0 age valid: none
Code
<form onsubmit={() => signup(state)}>
  <Field label="Email" required>
    <Input type="email" value={state.email} />
    <FieldError slot="error" match="valueMissing">Enter your email.</FieldError>
    <FieldError slot="error" match="typeMismatch">That is not an email address.</FieldError>
  </Field>
  <Field label="Handle" validate={checkHandle} validationMode="onChange" validationDebounceTime={300}>
    <Input value={state.handle} attrs={{ minlength: 3 }} />
  </Field>
  <Field label="Age" validate={checkAge} validationMode="onBlur" description="Whole years."
         onValidityChange={(v) => log(v.valid, v.error, v.value, v.initialValue)}>
    <Input type="number" value={state.age} />
  </Field>
  <Button type="submit" variant="primary">Sign up</Button>
</form>

Async validation and form values

validate() may return a Promise, such as a server check; its message shows when it resolves. Its third argument holds the form's values by name.

Try admin, then leave the field.

Code
function checkAvailable(value: string): Promise<string | null> {
  return fetch(`/api/handles/${value}`).then((r) => r.ok ? "That username is taken." : null);
}

<Field label="Username" validate={checkAvailable} validationMode="onBlur">
  <Input value={state.username} />
</Field>

// formValues (third) has the form's values by name.
function checkConfirm(value: string, control: HTMLElement, formValues: FormValues): string | null {
  return value && value !== formValues["new-password"] ? "Passwords do not match." : null;
}

<Field label="Confirm password" validate={checkConfirm} validationMode="onBlur">
  <Input type="password" value={state.confirm} />
</Field>

Fieldset

A native fieldset with a legend groups related fields for assistive tech.

Address Information

We need your address to deliver your order.

Code
<FieldSet>
  <FieldLegend>Address Information</FieldLegend>
  <FieldDescription>We need your address to deliver your order.</FieldDescription>
  <FieldGroup>
    <Field label="Street Address"><Input value={address.street} /></Field>
    <div class="grid">
      <Field label="City"><Input value={address.city} /></Field>
      <Field label="Postal Code"><Input value={address.zip} /></Field>
    </div>
  </FieldGroup>
</FieldSet>

Payment method

Fieldsets, a separator, selects, a checkbox and a textarea in one form. A required Select shows its error like any other control.

Payment Method

All transactions are secure and encrypted

Enter your 16-digit card number

01
02
03
04
05
06
07
08
09
10
11
12
Pick a month.
2026
2027
2028
2029
2030
2031
Pick a year.
Enter the CVV.
Billing Address

The billing address associated with your payment method

paid: 0
Code
<form onsubmit={() => pay(payment)}>
  <FieldGroup>
    <FieldSet>
      <FieldLegend>Payment Method</FieldLegend>
      <FieldDescription>All transactions are secure and encrypted</FieldDescription>
      <FieldGroup>
        <Field label="Name on Card" required><Input value={payment.name} /></Field>
        <Field label="Card Number" description="Enter your 16-digit card number" required>
          <Input value={payment.card} attrs={{ inputmode: "numeric", pattern: "[0-9 ]{16,19}" }} />
        </Field>
        <div class="grid is-3">
          <Field label="Month" required>
            <Select value={payment.month} items={MONTHS} placeholder="MM" block />
            <FieldError slot="error" match="valueMissing">Pick a month.</FieldError>
          </Field>
          <Field label="Year" required>
            <Select value={payment.year} items={YEARS} placeholder="YYYY" block />
            <FieldError slot="error" match="valueMissing">Pick a year.</FieldError>
          </Field>
          <Field label="CVV" required>
            <Input value={payment.cvv} attrs={{ inputmode: "numeric" }} />
            <FieldError slot="error" match="valueMissing">Enter the CVV.</FieldError>
          </Field>
        </div>
      </FieldGroup>
    </FieldSet>
    <FieldSeparator />
    <FieldSet>
      <FieldLegend>Billing Address</FieldLegend>
      <FieldDescription>The billing address associated with your payment method</FieldDescription>
      <Field orientation="horizontal" label="Same as shipping address">
        <Checkbox checked={payment.sameAsShipping} />
      </Field>
    </FieldSet>
    <FieldSeparator />
    <Field label="Comments"><Textarea value={payment.comments} /></Field>
    <Field orientation="horizontal">
      <Button type="submit" variant="primary">Submit</Button>
      <Button type="reset">Cancel</Button>
    </Field>
  </FieldGroup>
</form>

A control's own message

A control that checks itself, such as NumberInput's step check, keeps its message inside a Field, which shows it like any other.

Sold in packs of 5, starting at 1. Off a pack size, the message names the two nearest.

Code
<Field label="Quantity" description="Sold in packs of 5, starting at 1. Off a pack size, the message names the two nearest." validationMode="onBlur">
  <NumberInput value={order.quantity} min={1} step={5} />
</Field>

Checkbox, radio and switch

Horizontal fields put the label beside the control.

Show these items on the desktop

Select the items you want to show on the desktop.

Your Desktop & Documents folders are synced with iCloud Drive.

Subscription Plan

Yearly and lifetime plans offer significant savings.

Require a second step when you sign in.

Code
<FieldSet>
  <FieldLegend variant="label">Show these items on the desktop</FieldLegend>
  <Field orientation="horizontal" label="Hard disks"><Checkbox checked={prefs.hard} /></Field>
  <Field orientation="horizontal" label="External disks"><Checkbox checked={prefs.external} /></Field>
</FieldSet>

<Field orientation="horizontal" label="Sync Desktop & Documents folders"
       description="Your folders are synced to iCloud Drive.">
  <Checkbox checked={prefs.sync} />
</Field>

<Field orientation="horizontal">
  <FieldContent>
    <FieldLabel>Multi-factor authentication</FieldLabel>
    <FieldDescription>Require a second step when you sign in.</FieldDescription>
  </FieldContent>
  <Switch checked={prefs.mfa} />
</Field>

Choice cards

A FieldLabel wrapping a Field makes the whole card the label.

compute: kubernetes
Code
<RadioGroup value={prefs.compute} attrs={{ "aria-label": "Compute environment" }}>
  <FieldLabel>
    <Field orientation="horizontal" reserveError={false}>
      <FieldContent>
        <FieldTitle>Kubernetes</FieldTitle>
        <FieldDescription>Run GPU workloads on a K8s cluster.</FieldDescription>
      </FieldContent>
      <RadioGroupItem value="kubernetes" />
    </Field>
  </FieldLabel>
  ...
</RadioGroup>

Field group with separators

FieldSeparator divides sections, with optional text.

Get notified when a long task finishes.

Get notified when tasks you created have updates.

Or
Code
<FieldGroup>
  <Field orientation="horizontal" label="Responses" description="Get notified when ChatGPT responds."><Checkbox checked /></Field>
  <FieldSeparator />
  <Field orientation="horizontal" label="Tasks"><Checkbox /></Field>
  <FieldSeparator>Or</FieldSeparator>
  <Field label="Notes"><Textarea /></Field>
</FieldGroup>

Responsive

responsive is a column in a narrow group and a row in a wide one (a container query).

Provide your full name for identification.

We use it for receipts.

Code
<FieldGroup>
  <Field orientation="responsive" label="Name" description="Provide your full name for identification.">
    <Input value={profile.name} />
  </Field>
  ...
</FieldGroup>

Disabled and server errors

disabled reaches every control; error shows a message from elsewhere, such as the server.

Shown on your profile.

Code
<Field label="Name" disabled={state.disabled} error={state.serverError}>
  <Input value={state.name} />
</Field>

Disabled choices

disabled on a field or a field set dims each checkbox, switch and radio with its label.

Sync
Plan

Errors from a schema

errors takes the issues a schema validator returns; several render as a list and mark the field invalid.

Checked as you type.

  • Use at least 8 characters.
  • Include a number.
Code
// issues from a schema validator, such as Zod's error.issues
<Field label="Password" errors={passwordIssues(state.password)}>
  <Input type="password" value={state.password} />
</Field>

Error parts

FieldError match picks a ValidityState key; always shows it whatever the state, and valid shows once the field passes.

This coupon has expired.
Use at least two letters.
Looks good.
Code
<Field label="Coupon" invalid>
  <Input value="SPRING21" />
  <FieldError slot="error" match="always">This coupon has expired.</FieldError>
</Field>

<Field label="Nickname" validationMode="onChange">
  <Input value={state.nickname} attrs={{ minlength: 2 }} />
  <FieldError slot="error" match="tooShort">Use at least two letters.</FieldError>
  <FieldError slot="error" match="valid">Looks good.</FieldError>
</Field>

Controlled dirty and touched

dirty and touched take the state from a form library; unset, the field tracks them.

Marked dirty and touched by the caller.

dirty is false, so typing does not mark it.

Right to left

Label, description and a horizontal field follow the inline direction.

يظهر في ملفك الشخصي.

Reference Field in the manual Every part, attribute, slot, key and data attribute.
Prev ComboboxNext FileUpload
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026