liqui
Components

Field

A labelled form field whose control sits on its own glass surface.

Loading…

Installation

npx shadcn@latest add https://liqui.design/r/field.json

Installing more than one? Register the namespace once in components.json and drop the URLs:

components.json
{
  "registries": {
    "@liqui-design": "https://liqui.design/r/{name}.json"
  }
}
npx shadcn@latest add @liqui-design/field

Usage

import {
  Field,
  FieldControl,
  FieldDescription,
  FieldError,
  FieldLabel,
} from '@/components/ui/field';
<Field>
  <FieldLabel>Workspace name</FieldLabel>
  <FieldControl placeholder="acme-design" />
  <FieldDescription>Lowercase letters, numbers and dashes.</FieldDescription>
</Field>

Examples

Validation

Loading…

Base UI drives validity from the native constraint API, so match takes ValidityState keys — valueMissing, typeMismatch, patternMismatch and the rest.

Notes

The input is not the surface

The <input> stays a plain transparent element inside the glass content layer. Making the input itself the glass root would put the backdrop and tint layers inside a replaced element, where browsers won't render them — you'd get an input with no glass and no error explaining why.

Rings live on the surface

Focus and invalid rings belong to the glass, not the input, so they wrap the whole bezel instead of appearing inside it. The surface reacts to its child's state through has-*:

has-[input:focus-visible]:shadow-[0_0_0_3px_color-mix(in_srgb,var(--lq-accent)_35%,transparent)]
has-[input[data-invalid][data-touched]]:shadow-[0_0_0_2px_color-mix(in_srgb,var(--lq-danger)_55%,transparent)]

Note data-touched — the invalid ring only appears once the user has actually interacted, so a form doesn't open covered in red.

On this page