Skip to the content

ComponentsForm

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"

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.

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.

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.

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.

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.

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

Keyboard
KeyBehaviour

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.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--destructive-strongtext
--muted-foregroundtext
--secondary-foregroundtext