---
outline: deep
---

# Input

Inputs let users enter and edit text, numbers, dates, and other single-line values.

**`.l-input`** — Native HTML Element

```html
<l-form-field>
  <label>Email</label>
  <input
    type="email"
    placeholder="jane@acme.com"
  />
  <p class="l-hint">We'll never share it.</p>
</l-form-field>
```

[`l-form-field`](/elements/form-field) auto-styles a bare text `<input>` and wires the accessibility (label, hint, error, `aria-*`). Standalone, apply `.l-input` to the input yourself.

## Options

### Type

`.l-input` styles every text-like type. `date` and `time` get a custom picker icon; `search` gets a custom clear button — all with zero JavaScript.

```html
<div class="grid gap-4 max-w-sm">
  <l-form-field>
    <label>Text</label>
    <input
      type="text"
      placeholder="Placeholder text"
    />
  </l-form-field>
  <l-form-field>
    <label>Number</label>
    <input
      type="number"
      placeholder="Placeholder text"
    />
  </l-form-field>
  <l-form-field>
    <label>Password</label>
    <input
      type="password"
      value="supersecret"
    />
  </l-form-field>
  <l-form-field>
    <label>Date</label>
    <input type="date" />
  </l-form-field>
  <l-form-field>
    <label>Time</label>
    <input type="time" />
  </l-form-field>
  <l-form-field>
    <label>Search</label>
    <input
      type="search"
      value="This a value"
    />
  </l-form-field>
</div>
```

### States

Native `disabled` and `readonly`. Invalid is styled via `:user-invalid` (after interaction) or by setting `aria-invalid="true"` — inside `l-form-field` this is managed for you.

```html
<div class="grid gap-4 max-w-sm">
  <l-form-field>
    <label>Default</label>
    <input
      type="text"
      placeholder="Placeholder text"
    />
  </l-form-field>
  <l-form-field>
    <label>Read-only</label>
    <input
      type="text"
      value="Read-only value"
      readonly
    />
  </l-form-field>
  <l-form-field>
    <label>Disabled</label>
    <input
      type="text"
      value="Disabled value"
      disabled
    />
  </l-form-field>
  <l-form-field>
    <label>Invalid</label>
    <input
      type="email"
      value="not-an-email"
      aria-invalid="true"
    />
    <p class="l-error">Enter a valid email address.</p>
  </l-form-field>
</div>
```

### Size & radius

Override `--height` for the control height and `--border-radius` for the corners.

### Password toggle

Add `password-toggle` on `l-input-group` — a show/hide button is injected when JavaScript loads (`aria-pressed`, localized label, `Eye`/`EyeOff` icon). Without JavaScript the field stays a plain password input.

```html
<div class="max-w-sm">
  <l-form-field>
    <label>Password</label>
    <l-input-group password-toggle>
      <input
        type="password"
        value="supersecret"
        autocomplete="current-password"
      />
    </l-input-group>
  </l-form-field>
</div>
```

## Examples

### Icons & units

A replaced `<input>` can't hold children, so wrap it in `l-input-group`: the group draws the border and the inner input becomes borderless — no class needed. Children render in DOM order, so an `<l-icon>` before the input is a leading adornment, a `<span>` after it is a trailing one.

```html
<div class="grid gap-4 max-w-sm">
  <l-form-field>
    <label>Search</label>
    <l-input-group>
      <l-icon name="lucide:search"></l-icon>
      <input
        type="search"
        placeholder="Search"
      />
    </l-input-group>
  </l-form-field>
  <l-form-field>
    <label>Height</label>
    <l-input-group>
      <input
        type="number"
        placeholder="Placeholder text"
      />
      <span>cm</span>
    </l-input-group>
  </l-form-field>
  <l-form-field>
    <label>Email</label>
    <l-input-group>
      <l-icon name="lucide:mail"></l-icon>
      <input
        type="email"
        placeholder="jane@acme.com"
      />
    </l-input-group>
  </l-form-field>
</div>
```

### With a hint

`.l-hint` adds always-visible helper text, linked to the control via `aria-describedby`.

```html
<l-form-field>
  <label>Email</label>
  <input
    type="email"
    placeholder="jane@acme.com"
  />
  <p class="l-hint">We'll never share it.</p>
</l-form-field>
```

## Accessibility

### Criteria

- **Accessible name** — Must have an associated `<label>` (wrap the input or use `for`/`id`)
- **Input purpose** — Set `type` and `autocomplete` so the field communicates its purpose and benefits from autofill
- **Focus visible** — Keyboard focus shows a 2px outline via `:focus-visible`
- **Errors identified** — `aria-invalid` marks the field; pair it with a visible message linked via `aria-describedby`
- **Required state** — Native `required` communicates a mandatory field to assistive tech

### Rules

- Always pair the input with a `<label>` — wrap the input or link with `for`/`id`
- Never rely on `placeholder` as the label — it disappears on input and fails contrast
- Set the most specific `type` (`email`, `tel`, `url`, `number`, `search`, `date`, `time`) for the right keyboard and validation
- Adornment icons inside `l-input-group` are decorative — never put the field label in one; authored adornment buttons need an `aria-label` (the injected password toggle handles its own)

### Keyboard interactions

- `Tab` — Moves focus to the input
- `Esc` — On `type=search`, clears the field (native)

## API reference

### Importing

```css
@import 'luxen-ui/css/input';
```

```js
import 'luxen-ui/input-group';
```

The CSS covers everything except the password toggle; the JS import upgrades `l-input-group` with its behavior.

### Attributes & Properties

- **type**: `text | search | number | password | email | url | tel | date | time` — Native input type. `date`/`time` get custom picker icons; `search` gets a custom clear button.
- **data-size**: `xs | sm | md | lg | xl` — Control height on the shared `--l-size-control-*` scale (default `md`). Affects only the height, not the label or hint/error.
- **placeholder** — Native placeholder text.
- **disabled** — Disables the input.
- **required** — Marks the input as required for form submission.
- **readonly** — Makes the input read-only.
- **aria-invalid** — Set to `true` to force the invalid style (otherwise applied via `:user-invalid`). `l-form-field` manages this automatically.

### CSS classes

- `.l-input` — Base input style, applied to a text-like `<input>` (text, search, number, password, email, url, tel, date, time, …). Inside `l-form-field` or `l-input-group` a bare text input is auto-styled, so the class is optional there.

### CSS custom properties

- `--height` (default: `var(--l-form-control-height)`) — Control height.
- `--border-radius` (default: `var(--l-form-control-border-radius)`) — Control border radius.
- `--calendar-icon` — Date picker glyph as a `url()`. Masked, so color is taken from the control, not the image.
- `--clock-icon` — Time picker glyph as a `url()`.
- `--clear-icon` — Search clear glyph as a `url()`.

### Input group

- **password-toggle**: `boolean` (default: `false`) — Inject a show/hide toggle button after the inner `input[type="password"]`.
- **size**: `'xs' | 'sm' | 'md' | 'lg' | 'xl' | undefined` (default: `md`) — Control size — maps the height to the shared `--l-size-control-*` scale ().

- `.l-input-group-toggle` — The injected show/hide password button.
