# OTP Field

Accessible one-time code input with customizable visual cells.

- Package: `@mielui/svelte`
- Component version: `2.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn

## Install

~~~~sh
pnpm dlx @mielui/svelte add otp-field
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Cell

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| id | string \| undefined | - | No | No | Id of the element. |
| style | string \| StyleProperties \| null \| undefined | - | No | No | Inline styles for the element. |
| cell | { char: string \| null \| undefined; isActive: boolean; hasFakeCaret: boolean; } | - | Yes | No | This specific cell, which is provided by the `cells` snippet prop from the `PinInput.Root` component. |
| element | HTMLElement \| null \| undefined | null | No | Yes | Bindable reference to the DOM element. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Group

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| id | string \| undefined | - | No | No | Id of the element. |
| style | string \| StyleProperties \| null \| undefined | - | No | No | Inline styles for the element. |
| disabled | boolean \| undefined | - | No | No | Whether the input is disabled |
| value | string \| undefined | '' | No | Yes | The value of the input. |
| onValueChange | OnChangeFn<string> \| undefined | - | No | No | A callback function that is called when the value of the input changes. |
| pasteTransformer | ((text: string) => string) \| undefined | - | No | No | A callback function that is called when the user pastes text into the input. It receives the pasted text as an argument, and should return the sanitized text. |
| textalign | "left" \| "center" \| "right" \| undefined | - | No | No | Customize the alignment of the text within in the input. |
| pushPasswordManagerStrategy | "increase-width" \| "none" \| undefined | - | No | No | How to handle the input when a password manager is detected. |
| length | number \| undefined | 6 | No | No | Number of characters in the code. |
| element | HTMLInputElement \| null \| undefined | null | No | Yes | Bindable reference to the DOM element. |
| onComplete | ((value: string) => void) \| undefined | - | No | No | Called with the code once every cell is filled. |
| children | Snippet<[{ cells: PinInputCell[]; isFocused: boolean; }]> \| undefined | - | No | No | Content rendered inside. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Separator

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { WithElement } from '@mielui/svelte/utils';
import type { PinInput } from 'bits-ui';
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import Root from './otp-field.svelte';
import Cell from './otp-field-cell.svelte';
import Group from './otp-field-group.svelte';
import Separator from './otp-field-separator.svelte';

export type OTPFieldCellState = PinInput.CellProps['cell'];
export type OTPFieldProps = Omit<
    PinInput.RootProps,
    'children' | 'child' | 'ref' | 'inputRef' | 'inputId' | 'maxlength' | 'onComplete'
> & {
    /** Number of characters in the code. */
    length?: number;
    /** Bindable reference to the DOM element. */
    element?: HTMLInputElement | null;
    /** Called with the code once every cell is filled. */
    onComplete?: (value: string) => void;
    /** Content rendered inside. */
    children?: Snippet<[{ cells: OTPFieldCellState[]; isFocused: boolean }]>;
};
export type OTPFieldCellProps = WithElement<Omit<PinInput.CellProps, 'child' | 'children'>>;
export type OTPFieldGroupProps = HTMLAttributes<HTMLDivElement>;
export type OTPFieldSeparatorProps = HTMLAttributes<HTMLSpanElement>;

export { Cell, Group, Root, Separator };
~~~~

## Examples

### Alphanumeric

~~~~svelte
<script lang="ts">
    import { Label } from '@mielui/svelte/components/label';
    import * as OTPField from '@mielui/svelte/components/otp-field';
</script>

<div class="flex flex-col gap-3">
    <Label for="recovery-code">Recovery code</Label>
    <OTPField.Root
        id="recovery-code"
        length={5}
        pattern="^[a-zA-Z0-9]*$"
        inputmode="text"
        autocapitalize="characters"
        pasteTransformer={(text) => text.trim().toUpperCase()}
    />
</div>
~~~~

### Disabled

~~~~svelte
<script lang="ts">
    import * as OTPField from '@mielui/svelte/components/otp-field';
</script>

<OTPField.Root aria-label="Verified code" value="482019" disabled />
~~~~

### Events

~~~~svelte
<script lang="ts">
    import * as OTPField from '@mielui/svelte/components/otp-field';

    let typed = $state('');
</script>

<div class="flex flex-col items-center gap-3">
    <OTPField.Root
        length={6}
        aria-label="Verification code"
        onValueChange={(value) => {
            typed = value;
        }}
    />
    <p class="text-sm text-foreground-muted">{typed.length} of 6 digits entered</p>
</div>
~~~~

### Form

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Label } from '@mielui/svelte/components/label';
    import * as OTPField from '@mielui/svelte/components/otp-field';

    let code = $state('');
    let result = $state('');
</script>

<form
    class="flex flex-col items-start gap-4"
    onsubmit={(event) => {
        event.preventDefault();
        result = `Submitted ${String(new FormData(event.currentTarget).get('code'))}`;
    }}
>
    <Label for="code-form">Security code</Label>
    <OTPField.Root
        id="code-form"
        name="code"
        length={6}
        minlength={6}
        required
        bind:value={code}
        pasteTransformer={(text) => text.replace(/[\s-]/g, '')}
    />
    <div class="flex gap-2">
        <Button type="submit">Verify</Button>
        <Button type="reset" variant="outline">Reset</Button>
    </div>
    <p role="status" class="text-sm text-foreground-muted">
        {result || 'You can paste a code containing spaces or hyphens.'}
    </p>
</form>
~~~~

For the rendered reference, visit [/docs/components/otp-field](/docs/components/otp-field).
