Qure UI
Components

Field

A label, a description and an error, tied to a control and driven by its real validity.

Printed at the top right of the requisition.

'use client'import { Field, FieldDescription, FieldLabel } from '@/registry/qure/ui/field'import { Input } from '@/registry/qure/ui/input'export default function FieldDemo() {  return (    <Field name="accession" className="max-w-sm">      <FieldLabel>Accession number</FieldLabel>      <Input placeholder="ACC-000000" />      <FieldDescription>Printed at the top right of the requisition.</FieldDescription>    </Field>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/field.json

Usage

import {
  Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel,
} from '@/components/ui/field'
<Field name="accession">
  <FieldLabel>Accession number</FieldLabel>
  <Input placeholder="ACC-000000" />
  <FieldDescription>Printed at the top right of the requisition.</FieldDescription>
</Field>

There is no state prop. The error appears because the control is invalid, and the label goes grey because the control is disabled — you describe the field, not its appearance.

This replaces the design system's InputField and CheckboxField. Those took a state prop and rendered the literal string "I am a label.", so they could show an error but never because one had happened. Anything still importing them is showing decoration, not state.

Composition

<Field>              {/* owns validity, disabled, name; renders a div */}
  <FieldLabel>       {/* a real <label>, htmlFor wired automatically */}
  <Input />          {/* any Base UI control registers itself here */}
  <FieldDescription> {/* a <p>, referenced by aria-describedby */}
  <FieldError />     {/* only in the DOM while the control is invalid */}
</Field>

Field does not render the control. Input, Textarea (through FieldControl), Checkbox, Switch, Select and the rest register themselves with the surrounding Field, which is what supplies the id, the aria-describedby and the data-invalid the stylesheets key off.

Two parts are easy to mix up:

PartUse it when
FieldControlYou want a native input the library has no wrapper for — type="date", type="file" — or you are rendering something else through render.
FieldItemYou have several controls in one Field, and each needs its own label and description. A checkbox list, a radio group.

FieldControl borrows Input's classes rather than restating them, so it takes the same size prop and cannot drift from Input's look. Pass size={null} when what you are rendering sets its own height, as a textarea does.

Validation

Type an incomplete number and tab away.

'use client'import { Field, FieldDescription, FieldError, FieldLabel } from '@/registry/qure/ui/field'import { Input } from '@/registry/qure/ui/input'export default function FieldValidation() {  return (    <Field      name="accession"      className="max-w-sm"      validationMode="onBlur"      validate={(value) =>        /^ACC-\d{6}$/.test(String(value ?? '')) ? null : 'Six digits, prefixed ACC-.'      }    >      <FieldLabel>Accession number</FieldLabel>      <Input placeholder="ACC-000000" defaultValue="ACC-42" />      <FieldDescription>Type an incomplete number and tab away.</FieldDescription>      <FieldError />    </Field>  )}

validate returns a string to fail and null to pass. validationMode decides when it runs — onSubmit by default, which is the wrong choice for a single field on its own; onBlur tells a radiographer that the accession number is short as they leave it, rather than after they submit.

FieldError with no match shows whatever the last validation produced. With a match it listens to one entry of the browser's own ValidityState, so match="valueMissing" and match="rangeOverflow" can carry different sentences without any JavaScript of yours running.

With a checkbox

Pulls the two most recent studies of the same modality for comparison.

De-identified before it leaves the department.

Unavailable until the patient consent form is on file.

'use client'import { Checkbox } from '@/registry/qure/ui/checkbox'import { Field, FieldDescription, FieldItem, FieldLabel } from '@/registry/qure/ui/field'export default function FieldCheckbox() {  return (    <Field className="max-w-sm">      <FieldItem>        <FieldLabel>          <Checkbox defaultChecked name="priors" />          Retrieve priors        </FieldLabel>        <FieldDescription>          Pulls the two most recent studies of the same modality for comparison.        </FieldDescription>      </FieldItem>      <FieldItem>        <FieldLabel>          <Checkbox name="teaching" />          Add to the teaching file        </FieldLabel>        <FieldDescription>De-identified before it leaves the department.</FieldDescription>      </FieldItem>      <FieldItem disabled>        <FieldLabel>          <Checkbox name="research" />          Share with the research cohort        </FieldLabel>        <FieldDescription>Unavailable until the patient consent form is on file.</FieldDescription>      </FieldItem>    </Field>  )}

Each row is a FieldItem — its own label, its own description, and disabled scoped to it. This is the composition the old CheckboxField was reaching for. Note the third row: the label and the description stay legible, because "unavailable until consent is on file" is information and hiding the option loses it.

With a switch

Pages the on-call radiologist as soon as the model flags a study.

Sent at 07:00 local time.

'use client'import { Field, FieldDescription, FieldItem, FieldLabel } from '@/registry/qure/ui/field'import { Switch } from '@/registry/qure/ui/switch'export default function FieldSwitch() {  return (    <Field className="max-w-sm">      <FieldItem>        <FieldLabel className="w-full justify-between">          Notify on critical findings          <Switch defaultChecked name="critical-alerts" />        </FieldLabel>        <FieldDescription className="ps-0">          Pages the on-call radiologist as soon as the model flags a study.        </FieldDescription>      </FieldItem>      <FieldItem>        <FieldLabel className="w-full justify-between">          Daily worklist digest          <Switch name="digest" />        </FieldLabel>        <FieldDescription className="ps-0">Sent at 07:00 local time.</FieldDescription>      </FieldItem>    </Field>  )}

A settings row puts the control at the end of the label rather than the start. Switch is the on/off control; if the row would not read sensibly with "on" and "off" after it, you want a Checkbox or a Toggle instead.

With a textarea

Dictated text is inserted here; edit before signing.

'use client'import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from '@/registry/qure/ui/field'import { Textarea } from '@/registry/qure/ui/textarea'export default function FieldTextarea() {  return (    <Field      name="impression"      className="max-w-md"      validationMode="onBlur"      validate={(value) =>        String(value ?? '').trim().length >= 10 ? null : 'An impression needs at least a sentence.'      }    >      <FieldLabel>Impression</FieldLabel>      {/* A bare <textarea> is not a Base UI control, so Field cannot label or          validate it. Rendering it through FieldControl registers it. */}      <FieldControl        size={null}        render={<Textarea rows={4} placeholder="No acute intracranial abnormality." />}      />      <FieldDescription>Dictated text is inserted here; edit before signing.</FieldDescription>      <FieldError />    </Field>  )}

A bare <textarea> is not a Base UI control, so a Field around it can neither label nor validate it. <FieldControl render={<Textarea />} size={null} /> registers it and keeps the Textarea's own look.

Disabled

Put disabled on Field and it takes precedence over the control's own. Label, description and control all pick up data-disabled from the same source, so they cannot disagree — which is how a form ends up with a live-looking label over a dead input.

API Reference

Everything Base UI's Field accepts. The ones you will reach for on Field:

Prop

Type

On FieldError:

Prop

Type

On FieldControl:

Prop

Type

On this page