Acrylic
Components

Field

The macOS 26 Form layout family — labelled rows with descriptions, errors, separators, and fieldsets, in vertical / horizontal / responsive orientations.

A macOS Form is a layout, not a control: each row pairs a label with a control on a shared baseline, rows stack with a uniform vertical rhythm, and 1px hairline separators divide them. The Field family is that layout, expressed as composable parts. Spacing and typography are lifted from the Apple macOS 26 UI Kit; colors flip light/dark via the Acrylic theme tokens. API-compatible with the shadcn Field.

Installation

npx shadcn add https://acrylic-ui.vercel.app/r/field.json

Usage

macOS rows — label leading, control filling the right, caption below — stacked in a FieldGroup, with the last one invalid showing a FieldError:

Used for sign-in and receipts.

import { Input } from "@/components/acrylic/input"
import {
  Field,
  FieldDescription,
  FieldGroup,
  FieldLabel,
} from "@/components/acrylic/field"

<FieldGroup>
  <Field>
    <FieldLabel htmlFor="email">Email</FieldLabel>
    <Input id="email" type="email" placeholder="ada@example.com" />
    <FieldDescription>We'll never share your email.</FieldDescription>
  </Field>
</FieldGroup>

Examples

The macOS "settings card" (Mode 2): a Card-wrapped FieldGroup of rows, each label-left / trailing-control-right (grid-cols-[1fr_auto]) divided by hairlines, with a range of trailing accessories:

Trailing accessory options

Label
Label
3
30
seconds

Sizes — the label follows the control

Field takes the same size axis as the controls (mini · small · medium (default) · large · xl), and the label reads it off the row. Declare it on both halves — <Field size="xl"> + <Input size="xl"> — and the two agree. Skipping it on the Field is what makes a 13px label sit next to a 17px value:

The description steps down with it, never up to the label.

The description steps down with it, never up to the label.

The description steps down with it, never up to the label.

The description steps down with it, never up to the label.

Notes

  • Exports Field, FieldLabel, FieldTitle, FieldDescription, FieldError, FieldContent, FieldGroup, FieldSet, FieldLegend, FieldSeparator.
  • OrientationField takes orientation: horizontal (default, the macOS row — label hugs the left, the control fills the right, and the description/error sit below the control, aligned under it), vertical (label over control — use for wide controls / textareas), or responsive (vertical until the enclosing FieldGroup is ≥ ~28rem wide, then the macOS row — via container queries, so it adapts to its container, not the viewport). For a control row (title + subtitle on the left, control hugging the right) wrap the text in FieldContent and add className="grid-cols-[1fr_auto]" to the Field.
  • SizeField takes size: mini · small · medium (default) · large · xl, named to match Input/Select/Combobox so a row declares one size on both halves. FieldLabel/FieldTitle scale with it (10/11/13/15/17, each with its tracking companion) and FieldDescription steps up one notch on large/xl so it stays a subtitle. The label can't see its control, which is why the axis lives on Field — pair an xl control with a default Field and the label renders 4px smaller than the value beside it.
  • ErrorsFieldError accepts an errors array of { message } objects or plain children, and renders nothing when there's neither. Set data-invalid on the Field to tint its label/control toward the destructive color.
  • SeparatorsFieldSeparator is a 1px hairline (--acr-field, the exact kit separator color); pass inline children to render a label centered over the line.
  • GroupingFieldContent stacks a control with its title/description for horizontal rows; FieldSet + FieldLegend group related fields (FieldLegend variant: legend default, or label for a smaller heading).
  • Label alignmentFieldGroup takes variant: rows (default, macOS settings rows — each label hugs the left of its own row) or aligned (the classic macOS dialog form — one shared label column, labels right-aligned to the widest so every control starts at the same x and the inputs are equal width; implemented with a CSS subgrid).

On this page