@loke/ui
Version:
154 lines (112 loc) • 4.44 kB
Markdown
---
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