---
name: label
type: core
domain: forms
requires: [loke-ui]
description: >
  Label primitive wrapping Primitive.label. Prevents text selection on double-click via
  mousedown prevention when target is not a nested form control. Associate with Checkbox,
  Switch, RadioGroupItem via htmlFor or wrapping. Do not use plain HTML label.
---

# Label

`@loke/ui/label` — thin wrapper over `Primitive.label` that prevents accidental text selection on double-click, while still forwarding all standard label props and `ref`.

**Exports:** `Label`

## Setup

```tsx
import { Checkbox, CheckboxIndicator } from "@loke/ui/checkbox";
import { Label } from "@loke/ui/label";

function TermsField() {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
      <Checkbox id="terms" name="terms">
        <CheckboxIndicator>✓</CheckboxIndicator>
      </Checkbox>
      <Label htmlFor="terms">I accept the terms and conditions</Label>
    </div>
  );
}
```

## Core Patterns

### htmlFor association

Use `htmlFor` when the Label does not wrap the control. Clicking the label activates the associated control by `id`.

```tsx
<Label htmlFor="email">Email address</Label>
<input id="email" type="email" />
```

### Wrapping a Switch

When wrapping, `htmlFor` is optional — the label activates the first focusable descendant.

```tsx
import { Switch, SwitchThumb } from "@loke/ui/switch";
import { Label } from "@loke/ui/label";

<Label>
  <Switch name="notifications">
    <SwitchThumb />
  </Switch>
  Push notifications
</Label>
```

### Labelling a RadioGroup item

Each `RadioGroupItem` needs its own `Label`. Use `htmlFor` pointing to the item's `id`:

```tsx
import { RadioGroup, RadioGroupItem, RadioGroupIndicator } from "@loke/ui/radio-group";
import { Label } from "@loke/ui/label";

<RadioGroup name="plan">
  <div style={{ display: "flex", gap: 8 }}>
    <RadioGroupItem id="plan-free" value="free">
      <RadioGroupIndicator />
    </RadioGroupItem>
    <Label htmlFor="plan-free">Free</Label>
  </div>
  <div style={{ display: "flex", gap: 8 }}>
    <RadioGroupItem id="plan-pro" value="pro">
      <RadioGroupIndicator />
    </RadioGroupItem>
    <Label htmlFor="plan-pro">Pro</Label>
  </div>
</RadioGroup>
```

## Common Mistakes

### 1. Using a plain HTML `<label>` instead of the Label primitive

**Wrong:**

```tsx
<label htmlFor="agree">Accept terms</label>
```

**Correct:**

```tsx
import { Label } from "@loke/ui/label";
<Label htmlFor="agree">Accept terms</Label>
```

A plain `<label>` does not prevent double-click text selection. The Label primitive intercepts `mousedown` and calls `preventDefault` when `event.detail > 1` and the click target is not a form control — eliminating the flash of selected text users see when double-clicking label text.

Source: `src/components/label/label.tsx` — mousedown handler

### 2. Forgetting htmlFor when label does not wrap the control

**Wrong:**

```tsx
<Label>Accept terms</Label>
<Checkbox id="agree" name="agree">
  <CheckboxIndicator>✓</CheckboxIndicator>
</Checkbox>
```

The label text is visible but clicking it does not activate the checkbox. There is no ARIA association either, so screen readers do not link the two.

**Correct:**

```tsx
<Label htmlFor="agree">Accept terms</Label>
<Checkbox id="agree" name="agree">
  <CheckboxIndicator>✓</CheckboxIndicator>
</Checkbox>
```

Source: `src/components/label/label.tsx` — standard HTML label `for` attribute behavior

### 3. Wrapping non-form elements with Label

The mousedown prevention logic checks whether the click target is `button, input, select, textarea`. Wrapping arbitrary interactive elements (e.g., custom divs, anchors) results in unexpected `mousedown` prevention.

**Wrong:**

```tsx
<Label>
  <div role="button" onClick={handleClick}>Custom button</div>
  Section label
</Label>
```

**Correct:** Only use `Label` to wrap or reference native form controls (`<input>`, `<button>`, `<select>`, `<textarea>`) or the `@loke/ui` form primitives (`Checkbox`, `Switch`, `RadioGroupItem`) which render as `<button>` elements.

Source: `src/components/label/label.tsx` — `target.closest("button, input, select, textarea")`

## Cross-references

- **Checkbox** (`@loke/ui/checkbox`) — primary use case for Label
- **Switch** (`@loke/ui/switch`) — use Label for accessible switch naming
- **Radio Group** (`@loke/ui/radio-group`) — label each RadioGroupItem individually
