Label
A label names a control. Click it and the control responds, and it dims along with a disabled control.
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function LabelDemo() {
return (
<>
<div className="flex w-56 flex-col gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="you@example.com" />
</div>
<Label>
<Checkbox defaultChecked />
Subscribe to updates
</Label>
<Label>
<Checkbox disabled />
Disabled option
</Label>
<div className="flex w-56 flex-col gap-2">
<Label htmlFor="locked" disabled>
Locked field
</Label>
<Input id="locked" placeholder="Disabled" disabled />
</div>
</>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { cx } from "@/lib/cx";
// A label for a control. Use htmlFor to point at an input, or wrap the control inside it so
// clicking the text toggles it. It dims itself when it wraps a disabled control, or when you
// pass `disabled` for a control that sits next to it.
const base = [
"inline-flex items-center gap-2 text-base font-medium text-text select-none",
"data-[disabled]:cursor-not-allowed data-[disabled]:text-text-secondary",
"has-[[data-disabled]]:cursor-not-allowed has-[[data-disabled]]:text-text-secondary",
"has-[:disabled]:cursor-not-allowed has-[:disabled]:text-text-secondary",
].join(" ");
export function Label({
disabled,
className,
...props
}: ComponentProps<"label"> & { disabled?: boolean }) {
return <label data-disabled={disabled ? "" : undefined} className={cx(base, className)} {...props} />;
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
htmlFor | string | — | The id of the input this label names. Not needed when the control is inside the label. |
disabled | boolean | false | Dims the label. Use it for a disabled control that sits next to the label, not inside it. |
className | string | — | Extra classes, added after the label's own. |
Every other prop goes to the native label element.
The thinking
A label is the simplest component here, and the one most often skipped. It is just a native label, styled once.
- A real label: Because it is the native element, clicking it focuses or toggles its control with no script.
- Two ways to connect: Point
htmlForat an input's id, or wrap the control inside the label, which suits a checkbox, a switch or a radio. - It dims itself: Wrap a disabled control and the label dims with no prop. For a control beside it, pass disabled.
Interface design
- Text: 16px medium in the main text color.
- With a control inside: The control and the text sit in a row, 8px apart.
- Disabled: The secondary text color and a not-allowed cursor.
Interaction
- Click: Clicking the text focuses its input or toggles its checkbox, switch or radio.
- No selection: The text can't be selected, so a quick double click on a label doesn't highlight it.
- No states of its own: A label has no hover, focus or pressed state. The control it names shows those.
Empathy
- It is the name: A label gives its control the accessible name that a screen reader speaks.
- Keep it visible: Don't hide the label and rely on a placeholder. The label stays when someone starts typing.
- A bigger target: Wrapping a small control, like a checkbox, in a label makes the whole row clickable.