A caption for a form control — input, checkbox, radio, switch, select. Renders a native <label>. Pair every form control with a Label so the control has an accessible name, clicks on the label focus the control, and screen readers announce the field correctly.
<p>/<span>.aria-label on non-<input> widgets that don't support <label for> association.Either wrap the control inside the <Label> (implicit association — simplest) or set htmlFor to the control's id (explicit — required when the control isn't a child).
For the explicit form, React's useId() is the easiest way to generate a unique, SSR-stable id without hand-managing strings:
1import { useId } from "react";2 3function EmailField() {4 const id = useId();5 return (6 <div className="flex flex-col gap-1.5">7 <Label htmlFor={id}>Email</Label>8 <Input id={id} type="email" />9 </div>10 );11}When you're using TanStack Form, field.name is already a stable, unique string for each field — pass it as both htmlFor on the Label and id on the input. You get a guaranteed-correct association for free without calling useId() separately:
1<form.Field name="email">2 {(field) => (3 <div className="flex flex-col gap-1.5">4 <Label htmlFor={field.name}>Email</Label>5 <Input6 id={field.name}7 name={field.name}8 type="email"9 value={field.state.value}10 onBlur={field.handleBlur}11 onChange={(event) => field.handleChange(event.target.value)}12 />13 </div>14 )}15</form.Field>A Label automatically gets font-medium when it doesn't contain a nested form control (<input>, <textarea>, <select>, <button>, or [contenteditable]). The default is detected via :has() and applied through a :where()-wrapped variant so its specificity is 0 — any font-weight utility you pass on the Label itself overrides cleanly:
1<div className="flex flex-col gap-1.5">2 <Label htmlFor="email">Email</Label> {/* → font-medium */}3 <Input id="email" type="email" />4</div>5 6<div className="flex flex-col gap-1.5">7 <Label htmlFor="email" className="font-bold">Email</Label> {/* → font-bold */}8 <Input id="email" type="email" />9</div>When the Label wraps a control, the auto-default is skipped on purpose — otherwise the control's own typography would inherit the medium weight from the label. In that case, apply font-medium to your own caption element (a <span> or <p>) inside the label:
1<Label htmlFor="email" className="flex flex-col gap-1.5">2 <span className="font-medium">Email</span>3 <Input id="email" type="email" />4</Label>Pass disabled to render the label in a disabled style. Typically you'll want this to mirror the underlying control's disabled state so the visual treatment stays consistent.
1import { Input } from "@ngrok/mantle/input";2import { Label } from "@ngrok/mantle/label";3 4<div className="flex flex-col gap-1.5">5 <Label htmlFor="name" disabled>Name</Label>6 <Input id="name" type="text" disabled readOnly value="foo" />7</div>8 9<div className="flex items-center gap-2">10 <Label htmlFor="name" disabled>Name:</Label>11 <Input id="name" type="text" disabled readOnly value="foo" />12</div>The Label accepts the following props in addition to the standard HTML label attributes.