# In-field label

A small label fixed inside the top of its field, above the value.

- **Import:** `import { InFieldLabel } from "@booleanpress/ui/in-field-label"`
- **Page:** <https://ui.booleanpress.com/components/in-field-label> · @booleanpress/ui 0.2.0

## Usage

`InFieldLabel` wraps one field and its `<label>`. The label is 10 px, inside the top of the field at its start padding; the field grows to 47 px to make room above the value.

```tsx
import { InFieldLabel } from "@booleanpress/ui/in-field-label"
import { Input } from "@booleanpress/ui/input"
import { Label } from "@booleanpress/ui/label"

export function FromName() {
  return (
    <InFieldLabel>
      <Input id="from-name" />
      <Label htmlFor="from-name">From name</Label>
    </InFieldLabel>
  )
}
```

The field is an `Input`, an `InputNumber`, an `InputMask`, a `Textarea`, a `PasswordInput`, an `InputGroup`, a `NativeSelect` or a `Select`; the label is a `Label` (or a `label`) whose `htmlFor` is the field's `id`, a direct child of the wrapper. The label does not move, so a placeholder can show under it. An icon in an `InputGroup` addon lines up with the value, below the label. For a label that moves out of an empty field, use `FloatLabel`.

## Examples

### Basic

The label sits in the top of the field; an icon lines up with the value below it.

```tsx
import { MailIcon } from "lucide-react"
import { InFieldLabel } from "@booleanpress/ui/in-field-label"
import { Input } from "@booleanpress/ui/input"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@booleanpress/ui/input-group"
import { Label } from "@booleanpress/ui/label"

export default function InFieldLabelBasic() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InFieldLabel>
        <Input id="ifl-from-name" defaultValue="Acme Support" />
        <Label htmlFor="ifl-from-name">From name</Label>
      </InFieldLabel>
      <InFieldLabel>
        <InputGroup>
          <InputGroupInput id="ifl-from-email" placeholder="support@example.com" />
          <InputGroupAddon>
            <MailIcon />
          </InputGroupAddon>
        </InputGroup>
        <Label htmlFor="ifl-from-email">From email</Label>
      </InFieldLabel>
    </div>
  )
}
```

### Invalid

`aria-invalid` on the field colours the label `--destructive-strong`, focused or not.

```tsx
import { InFieldLabel } from "@booleanpress/ui/in-field-label"
import { Input } from "@booleanpress/ui/input"
import { Label } from "@booleanpress/ui/label"

export default function InFieldLabelInvalid() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-1.5">
      <InFieldLabel>
        <Input id="ifl-api-key" defaultValue="key_test" aria-invalid aria-describedby="ifl-api-key-error" />
        <Label htmlFor="ifl-api-key">API key</Label>
      </InFieldLabel>
      <p id="ifl-api-key-error" className="text-xs text-destructive-strong">
        A live API key starts with key_live_.
      </p>
    </div>
  )
}
```

### With a select

A `Select` takes the label too; its chevron stays centred in the field.

```tsx
import { InFieldLabel } from "@booleanpress/ui/in-field-label"
import { Label } from "@booleanpress/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@booleanpress/ui/select"

export default function InFieldLabelWithSelect() {
  return (
    <InFieldLabel className="w-full max-w-sm">
      <Select defaultValue="weekly">
        <SelectTrigger id="ifl-digest" className="w-full">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="daily">Every day</SelectItem>
          <SelectItem value="weekly">Every week</SelectItem>
          <SelectItem value="monthly">Every month</SelectItem>
        </SelectContent>
      </Select>
      <Label htmlFor="ifl-digest">Delivery report</Label>
    </InFieldLabel>
  )
}
```

### With a number and a mask

An `InputNumber`, its suffix moving down with the number, and an `InputMask`.

```tsx
import { InFieldLabel } from "@booleanpress/ui/in-field-label"
import { InputMask } from "@booleanpress/ui/input-mask"
import { InputNumber } from "@booleanpress/ui/input-number"
import { Label } from "@booleanpress/ui/label"

export default function InFieldLabelWithNumberAndMask() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InFieldLabel>
        <InputNumber id="ifl-monthly-quota" fluid defaultValue={25000} suffix="emails" />
        <Label htmlFor="ifl-monthly-quota">Monthly quota</Label>
      </InFieldLabel>
      <InFieldLabel>
        <InputMask id="ifl-vat-number" mask="aa999999999" placeholder="GB123456789" />
        <Label htmlFor="ifl-vat-number">VAT number</Label>
      </InFieldLabel>
    </div>
  )
}
```

## Accessibility

**Semantics.** A `div` round the field and a native `label`. The wrapper adds no role.

**Labels.** The label names the field through `htmlFor` and the field's `id`: it is the field's visible and accessible name.

**Focus.** The wrapper takes no focus. The label ignores the pointer, so a click on it reaches the field under it. Focus inside the wrapper colours the label `--secondary-foreground`.

**Known limits.**

- The 10 px label is small; keep it short.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### InFieldLabel

Renders a `div` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="in-field-label"` (InFieldLabel).

## Theming

The label is `--muted-foreground`, `--secondary-foreground` while the field has focus and `--destructive-strong` while the field is invalid.

| Token | Used for |
| --- | --- |
| `--destructive-strong` | text |
| `--muted-foreground` | text |
| `--secondary-foreground` | text |
