Skip to main content
Markdown

Label

A lightweight text label for section titles, metadata, and explicit control associations when a full Field layout is not needed.

For complete form fields, prefer Field. It wires the label, control, description, error message, required/invalid/disabled/read-only state, and ARIA ids for you. Use standalone Label when you only need the label primitive.

Usage

import { Label } from '@takeoff-ui/react-spar';
<Label as="span">Payment Method</Label>

For native controls, point htmlFor at the control id:

<Label htmlFor="pnr">PNR</Label>
<input id="pnr" />

For Takeoff Input, manual association is possible but Field is usually the better choice. If you do wire it manually, set the base id on Input and point htmlFor at the field id generated by Spar (${id}-field):

<Label id="passenger-name-label" htmlFor="passenger-name-field">
Passenger name
</Label>
<Input id="passenger-name">
<Input.Field />
</Input>

Playground


function PlaygroundDemo() {
  return (
    <div className="grid w-full max-w-120 gap-3">
      <div className="rounded-xl border border-neutral-200 p-4">
        <Label
          as="h4"
          style={{
            fontFamily: 'var(--desktop-title-h4-font)',
            fontSize: 'var(--desktop-title-h4-size)',
            fontWeight: 'var(--desktop-title-h4-line-weight)',
            lineHeight: 'var(--desktop-title-h4-line-height)',
          }}
        >
          Payment Method
        </Label>
        <p className="m-0 text-sm text-neutral-600">
          All transactions are secure and encrypted.
        </p>
      </div>

      <div className="grid gap-2">
        <Label htmlFor="booking-reference">Booking reference</Label>
        <input
          id="booking-reference"
          className="rounded-lg border border-neutral-200 px-3 py-2 text-sm"
          placeholder="TK-1928"
        />
      </div>

      <div className="grid gap-2 rounded-xl border border-neutral-200 p-4">
        <Label id="passenger-name-label" htmlFor="passenger-name-field">Passenger name</Label>
        <Input id="passenger-name">
          <Input.Field placeholder="Ada Lovelace" />
        </Input>
      </div>
    </div>
  );
}

render(<PlaygroundDemo />);

States

required, optional, disabled, readOnly, and invalid are emitted as data-* attributes on the label for styling. They do not set required, disabled, readOnly, or aria-invalid on the related control. Set the same state on the control or use Field when the label and control should stay synchronized. Required labels render a decorative asterisk after the text.


function StatesDemo() {
  return (
    <div className="grid w-full max-w-90 gap-3">
      <div className="grid gap-1">
        <Label htmlFor="required-native" required>Required label</Label>
        <input id="required-native" required className="rounded-lg border border-neutral-300 px-3 py-2 text-sm" placeholder="Required field" />
      </div>

      <div className="grid gap-1">
        <Label htmlFor="optional-native" optional>Optional label</Label>
        <input id="optional-native" className="rounded-lg border border-neutral-300 px-3 py-2 text-sm" placeholder="Optional field" />
      </div>

      <div className="grid gap-1">
        <Label htmlFor="disabled-native" disabled>Disabled label</Label>
        <input id="disabled-native" disabled className="rounded-lg border border-neutral-300 px-3 py-2 text-sm" placeholder="Disabled field" />
      </div>

      <div className="grid gap-1">
        <Label htmlFor="readonly-native" readOnly>Read-only label</Label>
        <input id="readonly-native" readOnly className="rounded-lg border border-neutral-300 px-3 py-2 text-sm" defaultValue="Read-only value" />
      </div>

      <div className="grid gap-1">
        <Label htmlFor="invalid-native" invalid>Invalid label</Label>
        <input id="invalid-native" aria-invalid="true" className="rounded-lg border border-danger-base px-3 py-2 text-sm" defaultValue="Invalid value" />
      </div>
    </div>
  );
}

render(<StatesDemo />);

Label vs Field

Label is intentionally small. If you need helper text, error text, invalid styling, or shared disabled/read-only/required state, use Field.Label inside Field. That is the default path for form controls.


function FieldComparisonDemo() {
  return (
    <Field className="w-full max-w-90" required>
      <Field.Label>Passenger email</Field.Label>
      <Input>
        <Input.Field type="email" placeholder="you@example.com" />
      </Input>
      <Field.Description>
        Use Field when the label, helper text, invalid state, and control should be wired together.
      </Field.Description>
    </Field>
  );
}

render(<FieldComparisonDemo />);

API Reference

Label

Props

NameTypeDefaultDescription
childrenReact.ReactNode-Label content.
classNamesPartial<Record<"root", string>>-Per-slot extra classes.
slotPropsPartial<Record<"root", React.HTMLAttributes<HTMLElement>>>-Per-slot HTML-attribute overrides.
disabledbooleanfalseMarks label for a disabled field (exposed via data attribute for styling)
requiredbooleanfalseMarks label for a required field (exposed via data attribute for styling)
readOnlybooleanfalseMarks label for a read-only field (exposed via data attribute for styling)
invalidbooleanfalseMarks label for an invalid field (exposed via data attribute for styling)
optionalbooleanfalseMarks label for an optional field (exposed via data attribute for styling)
classNamestring-Appends custom classes to the root slot.

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-requiredrequired is true.Styling hook for required labels.
data-optionaloptional is true.Styling hook for optional labels.
data-disableddisabled is true.Styling hook for disabled labels.
data-readonlyreadOnly is true.Styling hook for read-only labels.
data-invalidinvalid is true.Styling hook for invalid labels.