---
outline: deep
---

# Form Field

Wraps a label, a control, and optional messages — wiring the accessibility plumbing (`id`/`for`, `aria-describedby`, `aria-invalid`, required marker) and choosing an inline or stacked layout from the control type.

**`<l-form-field>`** — Progressive Custom Element

```html
<l-form-field>
  <label>I accept the terms and conditions</label>
  <input
    type="checkbox"
    required
  />
  <p class="l-hint">Read them before checking this box.</p>
  <p class="l-error">You must accept the terms to continue.</p>
</l-form-field>
```

Children may appear in any order — the field finds the `<label>`, the control (`input` / `select` / `textarea`), `.l-hint`, and `.l-error` by selector.

## Options

### Layout

Set automatically from the control: toggle controls (checkbox, radio, switch) get an inline layout (control then label); other controls stack (label above).

```html
<l-form-field>
  <label>I accept the terms and conditions</label>
  <input
    type="checkbox"
    required
  />
  <p class="l-hint">Read them before checking this box.</p>
  <p class="l-error">You must accept the terms to continue.</p>
</l-form-field>
```

### Stacked

`select`, `input`, and `textarea` stack the label above the control.

```html
<l-form-field>
  <label>Country</label>
  <select class="l-select">
    <option>United States</option>
    <option>France</option>
    <option>Japan</option>
  </select>
  <p class="l-hint">Where the order will be shipped.</p>
</l-form-field>
```

### Required

Add native `required` to the control. The field reflects `required` and appends the marker to the label.

### Hint

Add a `<p class="l-hint">` for always-visible helper text. The field links it to the control with `aria-describedby`.

### Error

Add a `<p class="l-error">` for the validation message. It stays hidden until the control is invalid after interaction, then the field reveals it, sets `aria-invalid`, adds it to `aria-describedby`, and gives it `role="alert"` so it is announced.

### Unstyled

Add `unstyled` to keep the accessibility wiring while opting out of auto-styling the control — useful for a third-party control or your own styling.

## Accessibility

### Criteria

- **Label association** — Links `<label for>` to the control `id`, generating an `id` when missing
- **Description** — Links `.l-hint` and the visible `.l-error` to the control via `aria-describedby`
- **Error identification** — Sets `aria-invalid` and announces `.l-error` (`role=alert`) once invalid after interaction

### Rules

- Put exactly one control (input / select / textarea) per `l-form-field`
- Provide a `<label>`; use `.l-hint` for guidance and `.l-error` for the validation message

## API reference

### Importing

```css
@import 'luxen-ui/css/form-field';
```

```js
import 'luxen-ui/form-field';
```

### Attributes & Properties

- **layout**: `'inline' | 'stacked' | undefined` — Layout, derived from the control type (`inline` for checkbox/radio/switch, otherwise `stacked`). Set one to force it.
- **required**: `boolean` (default: `false`) — Reflected when the control is required. Drives the label marker.
- **optional**: `boolean` (default: `false`) — Reflected when the control is not required.
- **invalid**: `boolean` (default: `false`) — Reflected once the control is invalid after interaction (or set to force the invalid state).
- **unstyled**: `boolean` (default: `false`) — Opt out of auto-styling the control; the ARIA wiring is preserved.

### Slots

- **(default)** — A `<label>`, one form control (a native `input` / `select` / `textarea`, or a form-associated custom element such as `l-slider`), and optional `.l-hint` / `.l-error` message elements, in any order.

### CSS classes

- `.l-hint` — Helper text element. Always visible; linked via `aria-describedby`.
- `.l-error` — Error message element. Hidden until invalid, then revealed with `role="alert"` and linked via `aria-describedby`.
