PIN Input
One-time-code / PIN entry.
Usage
maxlength is required — it is how many cells there are. Root hands you
a cells array through a snippet, so you render them. Use onComplete to submit rather than making the user hunt for a button after
typing the last digit.
value: (empty)
One input, not six
The cells look like six boxes. They are not six inputs — Bits renders one real input behind them and the cells are purely visual. That is the whole reason to use this instead of rolling your own, and it buys four things a six-input version loses:
- Paste works. Pasting a six-digit code fills every cell at once.
- Mobile SMS autofill works — the input carries
autocomplete="one-time-code", so the OS offers the code from the message. - No tabbing between boxes, and no focus-juggling bugs to write.
- A screen reader announces one field, not six unlabelled ones.
Use pasteTransformer to clean what arrives — stripping hyphens and
spaces means a code copied as 123-456 still lands correctly.
Naming it
<label for> does not work here.
The id you pass lands on the wrapper <div>, and the real input gets a Bits-internal id you cannot
predict — so a for attribute has nothing to point at, and you ship an unnamed
field. Verified against the rendered DOM, and pinned by a test.
Name it from Root instead; the spread reaches the input:
aria-label="Verification code"— simplest.aria-labelledby="my-label-id"— when you want a visible label. Give your own element an id and point at it, as the example above does.
Props
PinInput.Root — value is bindable.
| Prop | Type | Default |
|---|---|---|
value The entered code. Bindable. | string | '' |
invalid Marks the control as failing validation. Always applies the invalid styling. Does NOT set `aria-invalid`: this renders as `container; Bits owns the inputs`, and ARIA does not support the attribute there, so assistive technology is free to ignore it. axe does NOT flag it either way — verified by injecting it and watching the suite still pass — so this is a decision taken from the spec, not one a tool enforces. The accessible signal comes from `Field` wiring the error message through `aria-describedby`. Prefer letting `Field` drive this: passing `Field` an `error` is what makes a field invalid, so the message the user reads and the state of the control cannot disagree. | boolean | false |
class | string | — |
children bits-ui | Snippet<[PinInputRootSnippetProps]> | undefined | — |
disabled bits-ui Whether the input is disabled | boolean | undefined | — |
id bits-ui | string | undefined | — |
inputId bits-ui Optionally provide an ID to apply to the hidden input element. | string | undefined | — |
inputRef bits-ui The underlying hidden `<input>` element. Bind to call `focus()`, read selection, etc. | HTMLInputElement | null | undefined | — |
maxlength required bits-ui The max length of the input. | number | — |
onComplete bits-ui A callback function that is called when the input is completely filled. | ((...args: any[]) => void) | undefined | — |
onValueChange bits-ui A callback function that is called when the value of the input changes. | OnChangeFn<string> | undefined | — |
pasteTransformer bits-ui 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. Use this function to clean up the pasted text, like removing hyphens or other characters that should not make it into the input. | ((text: string) => string) | undefined | — |
pushPasswordManagerStrategy bits-ui How to handle the input when a password manager is detected. | "increase-width" | "none" | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |
textalign bits-ui Customize the alignment of the text within in the input. | "center" | "right" | "left" | undefined | "left" |
PinInput.Cell takes the cell object from the
snippet plus class.
| Prop | Type | Default |
|---|---|---|
class Extra classes merged onto the cell. | string | — |
cell required bits-ui This specific cell, which is provided by the `cells` snippet prop from the `PinInput.Root` component. | PinInputCell | — |
child bits-ui | Snippet<[{ props: Record<string, unknown>; }]> | undefined | — |
children bits-ui | Snippet<[]> | undefined | — |
id bits-ui | string | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |