# InputOTP

A one-time code, one character per cell, advancing as it is typed.

```slint
import { InputOTP } from "@glint/components/input-otp.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 300px;
    background: Tokens.color-background;

    in-out property <string> latest-code: "";

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            InputOTP {
                length: 6;
                completed(code) => {
                    root.latest-code = code;
                }
            }
        }

        Text {
            text: root.latest-code != "" ? "Verified Code: " + root.latest-code : "Enter 6-digit code";
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

## Usage

```slint
import { InputOTP } from "@glint/components/input-otp.slint";

export component AppWindow inherits Window {
    VerticalLayout {
        alignment: center;

        InputOTP {
            length: 6;
            completed(code) => {
                // submit verification code
            }
        }
    }
}
```

`InputOTP` renders a segmented row of character cells for one-time verification codes. Keystrokes fill cells sequentially, `Backspace` clears and retreats, and `completed(string)` fires the moment the final cell is filled.

## Examples

### Input modes

`mode` restricts allowed characters:

- `InputOTPMode.numeric`: Accepts digits `0`–`9` only (ideal for SMS or authenticator codes).
- `InputOTPMode.alphanumeric`: Accepts digits and letters (ideal for license keys or mixed promo codes).

```slint
import { InputOTP, InputOTPMode } from "@glint/components/input-otp.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 340px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 20px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            VerticalLayout {
                spacing: 16px;
                alignment: center;

                InputOTP {
                    mode: InputOTPMode.numeric;
                    length: 4;
                }

                InputOTP {
                    mode: InputOTPMode.alphanumeric;
                    length: 6;
                }
            }
        }
    }
}
```

### Grouping with separators

Set `group-size` to insert separator rules between cell clusters (for example, `group-size: 3` splits 6 cells into two groups of 3).

```slint
import { InputOTP } from "@glint/components/input-otp.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 300px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            InputOTP {
                length: 6;
                group-size: 3;
            }
        }
    }
}
```

### Validation error state

Set `invalid: true` to display destructive borders across all cells and announce an invalid state.

```slint
import { InputOTP } from "@glint/components/input-otp.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 300px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            InputOTP {
                length: 6;
                invalid: true;
            }
        }
    }
}
```

## API Reference

### Properties

| Property     | Type              | Default                | Description                                                                                                                                                                                                                                                                                                         |
| ------------ | ----------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `length`     | `in int`          | `6`                    | Number of cells the code is split into (clamped to capacity).                                                                                                                                                                                                                                                       |
| `mode`       | `in InputOTPMode` | `InputOTPMode.numeric` | `numeric` (default) takes digits only; `alphanumeric` also takes letters.                                                                                                                                                                                                                                           |
| `group-size` | `in int`          | `0`                    | Cells per group, with a rule drawn between groups — `group-size: 3` on a six-cell field is the canonical 123-456. Zero (the default) lays every cell out in one run. The grouping is presentation like the cells themselves: the rule takes room and nothing else, and the field stays one tab stop with one value. |
| `enabled`    | `in bool`         | `true`                 | When false, the field dims and stops taking focus and keystrokes.                                                                                                                                                                                                                                                   |
| `invalid`    | `in bool`         | `false`                | When true, paints the cells in destructive state.                                                                                                                                                                                                                                                                   |
| `auto-focus` | `in bool`         | `false`                | When true, the field grabs focus on mount.                                                                                                                                                                                                                                                                          |
| `value`      | `out string`      | no default             | The combined code — the characters of every filled cell, in order.                                                                                                                                                                                                                                                  |
| `has-focus`  | `out bool`        | no default             | Whether the field currently holds keyboard focus.                                                                                                                                                                                                                                                                   |

### Callbacks

| Callback            | Description                                                      |
| ------------------- | ---------------------------------------------------------------- |
| `edited(string)`    | Fired with the combined code whenever it changes.                |
| `completed(string)` | Fired with the combined code the moment the last cell is filled. |
| `accepted(string)`  | Fired with the combined code when the user presses Enter.        |

### Functions

| Function                    | Description                                                                                                                                                                                                                                                                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `clear()`                   | Empties every cell — the way to reset the field from the outside, since `value` is derived and can't be assigned.                                                                                                                                                                                                                                |
| `insert(character: string)` | One character through the same path a keystroke takes: validated by `mode`, written to the frontier cell, firing `completed` when it fills the last one; anything else is ignored. This is the paste seam — Slint has no clipboard access and cannot split a string, so the host reads the clipboard and feeds the code one character at a time. |

### Enums

| Enum           | Values                    |
| -------------- | ------------------------- |
| `InputOTPMode` | `numeric`, `alphanumeric` |

## Accessibility

- **Unified text entry.** Assistive technology sees one `text-input` node with `accessible-value` containing the concatenated string, rather than multiple disconnected boxes.
- **`invalid` announces the word “Invalid”.** Slint 1.17 publishes no `accessible-invalid`, so the state rides the description channel ADR-0013 opened. In the current implementation, `accessible-description` becomes only `"Invalid"`; it is not appended to another description. A red border is not the announcement — this is.
- **Navigation.** The entire group acts as a single tab stop.
