Component · Core
Label
Overview
The visible text that identifies a form control's purpose — "Email," "Full name," "Message." Label is both a standalone native primitive — confirmed in use by Checkbox, Radio Group, and Switch, alongside Input and Textarea — and a required part of the shared form-field model documented on Input's page. It is not a general-purpose typography style — don't reach for Label in place of a heading, a caption, or arbitrary metadata text; those are plain text, not a control's name.
Anatomy
Label text → optional (optional) suffix.
- Label text — the visible name of the control it labels.
(optional)suffix — appended to the label text on optional fields.
Live Example
Code Example
import { Label } from "@/components/ui/label";
/**
* Live Label example — a Label bound to its control with `htmlFor` / `id` (the
* mandatory association), and the `(optional)` suffix for a non-required field.
* The bare <input> is a native element standing in for a form control: the
* Monogem Input component is documented separately and out of scope here.
*/
const fieldClass =
"h-10 rounded-md border-[length:var(--scale-px)] border-solid border-input bg-background px-3 text-sm leading-snug text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
export function LabelShowcase() {
return (
<div className="flex max-w-sm flex-col gap-6">
<div className="flex flex-col gap-2">
<Label htmlFor="label-example-name">Full name</Label>
<input
id="label-example-name"
type="text"
className={fieldClass}
placeholder="Ada Lovelace"
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="label-example-nickname" optional>
Nickname
</Label>
<input
id="label-example-nickname"
type="text"
className={fieldClass}
placeholder="Ada"
/>
</div>
</div>
);
}Usage Guidance
Default usage
A native <label> bound to its control via htmlFor/id. Always visible — no floating-label
pattern, and never replaced by a placeholder (see
Input's shared form-field model).
Label has no disabled styling of its own — when its control is disabled, the label dims as part of the same field unit; see Input's disabled treatment.
Required and optional indicators
Fields are required by default. Optional fields append (optional) after the label text, in
--muted-foreground — never a red asterisk on required fields. Full reasoning lives on
Input's shared form-field model: a red asterisk would
contradict Color's "red means danger, only" principle. The native
required attribute (not the label) carries the actual required-field semantics.
Content guidance
- Short noun phrase — "Email," not "Please enter your email address."
- Sentence case.
- No trailing colon ("Email", not "Email:").
Tokens
Label uses existing Monogem semantic tokens to stay consistent with the design system; it introduces no component-specific styling values.
| Token | Where used |
|---|---|
--foreground | Label text |
--muted-foreground | "(optional)" suffix |
Text uses the "Label / button" recipe (text-sm font-medium leading-snug) — the same recipe
Button uses for its own label, reused as-is.
Do / Don’t
Do
- Bind every Label to its control with
htmlFor/id. - Keep label text short and scannable.
- Mark optional fields, not required ones.
Don’t
- Use Label for headings, captions, or metadata unconnected to a form control.
- Rely on placeholder text or
aria-labelin place of a visible Label when one can be shown. - Add a red asterisk for required fields.
Accessibility
htmlFor/idpairing is mandatory — it's what lets a screen reader announce the label when the control receives focus, and what lets clicking the label focus/activate the control.- Only fall back to
aria-label(no visible text) when a visible Label truly can't be shown — a visible Label is always preferred.
Related Components / Patterns
- Input — the control a Label most often names.
- Checkbox — carries its own inline label.
- Radio Group — labels the group with a legend.