# Input

A single-line text field on a sunken fill, in four sizes, with an optional decorative icon before or after the text. Text is 16px on touch screens so iOS Safari doesn't zoom in on focus.

## Usage

### When to use

- Single-line text the user types: a name, an email address, a search term, a number.
- Values the user can copy but not edit, such as an API key, with readOnly.
- A field whose icon says what goes in, such as a magnifying glass for search, with leadingIcon.

### When not to use

- Text over several lines. Use Textarea.
- Picking one value from a known list. Use Select.
- A disabled field for a value the user should still read or copy. Use readOnly instead, so the field stays focusable and the value can be copied.

## Variants

### size

Height from size-element-*: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse. On touch, md grows to 36px, lg to 44px and xl to 48px; sm does not, so don't use sm on touch-first screens.

Values: `sm`, `md`, `lg`, `xl`. Default: `md`.

### type

Native input type for single-line text-like fields.

Values: `text`, `email`, `password`, `search`, `tel`, `url`, `number`. Default: `text`.

## Examples

### Default

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="email" className="text-sm text-text-primary">Email</label>
  <Input id="email" type="email" placeholder="you@example.com" />
</div>
```

### With an icon

The icon is decorative; the visible label names the field.

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="search-files" className="text-sm text-text-primary">Search files</label>
  <Input id="search-files" type="search" placeholder="Name or extension" leadingIcon={<IconSearch size={16} />} />
</div>
```

### Disabled

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="workspace" className="text-sm text-text-primary">Workspace</label>
  <Input id="workspace" disabled defaultValue="Personal" />
</div>
```

### Readonly

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="api-key" className="text-sm text-text-primary">API key</label>
  <Input id="api-key" readOnly defaultValue="wolf_sk_4f9a..." trailingIcon={<IconLock size={16} />} />
</div>
```

### Invalid

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="signup-email" className="text-sm text-text-primary">Email</label>
  <Input id="signup-email" type="email" aria-invalid="true" aria-describedby="signup-email-error" defaultValue="not-an-email" />
  <p id="signup-email-error" className="text-sm text-text-danger">Enter an email like you@example.com.</p>
</div>
```

## Do and don't

### Do

- Always pair the input with a visible label (htmlFor / id).
- Match the input size to the button next to it: md in forms, sm only in dense mouse-first UI (table filters, toolbars), lg or xl for the single main field of a screen (search hero, sign-in on mobile).
- Invalid: aria-invalid="true", the danger border, plus error text in color-text-danger linked with aria-describedby. Never color alone.
- Readonly: native readOnly, same look as default, still focusable with the focus ring. Use it for values the user can copy but not edit (API key, generated URL).
- Keep the danger border while focused; the focus ring is drawn outside it.
- Use leadingIcon for an icon that says what kind of value goes in: a magnifying glass for search, an envelope for email.
- Pass the icon with size and that match the input size: 16 in sm, md and lg, 20 in xl.

### Don't

- Don't use placeholder as the label or for required info; it disappears on typing.
- Don't use size sm on touch-first screens. On touch md is already 36px, lg 44px and xl 48px.
- Don't use disabled for values the user should still read or copy; use readOnly.
- Don't add a hover border change. border-control already marks the field.
- Don't drop below font-size-input or restyle colors through className.
- Don't let an icon carry meaning alone (a check mark for valid, a warning for invalid). Say it in text linked with aria-describedby.
- Don't put buttons or links in leadingIcon or trailingIcon. The icons are decorative and hidden from screen readers.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `size` | `enum` | `sm`, `md`, `lg`, `xl` | `md` | Height from size-element-*: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse. On touch, md grows to 36px, lg to 44px and xl to 48px; sm does not, so don't use sm on touch-first screens. |
| `disabled` | `boolean` |  | `false` | Not interactive. Neutral disabled look. |
| `readOnly` | `boolean` |  | `false` | The value can be read, selected and copied but not edited. Same look as default; keeps the focus ring. |
| `type` | `enum` | `text`, `email`, `password`, `search`, `tel`, `url`, `number` | `text` | Native input type for single-line text-like fields. |
| `leadingIcon` | `node` |  |  | Decorative Tabler icon before the text, such as a magnifying glass in a search field. Pass size and: 16 in sm, md and lg, 20 in xl. |
| `trailingIcon` | `node` |  |  | Decorative Tabler icon after the text, such as a lock on a readonly value. Same size rule as leadingIcon. Not for buttons: a clear or show-password button is planned as its own prop. |

### Slots

| Name | Required | Description |
| --- | --- | --- |
| `value` | no | Controlled or uncontrolled text value. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | The input element. Alone, it is the field box. With an icon it fills the group and draws no box of its own. |
| `placeholder` | Hint text shown when the value is empty. Colored with text-secondary, so it reaches 4.5:1 on the sunken fill. |
| `group` | The field box when leadingIcon or trailingIcon is set: a wrapper with the same fill, border, radius and height as the input alone. It shows the input's focus ring, disabled look and danger border through :has(). className lands here. |
| `groupIcon` | The leading or trailing icon inside the group. 16px in sm, md and lg, 20px in xl (DESIGN.md 9.1). Decorative. |

## Accessibility

Role: `textbox`

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the input and shows the focus ring. |
| `Any printable key` | Inserts text at the caret. |

- Pair with a visible label (htmlFor / id). Placeholder alone is not a label.
- Disabled uses the native disabled attribute, so the input leaves the tab order.
- Readonly uses the native readOnly attribute: still focusable and in the tab order, the value can be selected and copied.
- Invalid uses aria-invalid="true" plus visible error text in color-text-danger linked with aria-describedby.
- font-size-input is 14px on mouse and 16px on touch so iOS Safari does not zoom the page on focus.
- leadingIcon and trailingIcon are decorative (aria-hidden). The label and the text carry the meaning, so a screen reader hears the same field with or without them.
- A press on an icon or the padding of the group focuses the input, like a press on the field itself.

## Install

```bash
npx shadcn add https://werewolf-ui.vercel.app/r/input.json
```

This also adds `styles/werewolf.css`, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after `@import "tailwindcss"`, with a path relative to that CSS file (from `app/globals.css` it is `@import "../styles/werewolf.css"`).
