---
url: https://nsh.dev/ru/elements/otp-input
description: "Компонент OtpInput: числовой одноразовый код фиксированной длины, который вводится, вставляется или автозаполняется в одно нативное поле и отображается в виде ячеек, групп и каретки, стилизуемых приложением."
demo: https://nsh.dev/play/index.html#el-otp-input
---

# OtpInput

Поле ввода одноразового кода принимает фиксированное число цифр (код подтверждения из СМС, код из приложения-аутентификатора) и показывает их в ячейках. Оно отвечает за поведение: одно нативное текстовое поле, прозрачное и расположенное поверх ячеек, получает клавиатуру, поэтому набор текста, методы ввода, вставка, цифровая клавиатура и системное автозаполнение кода («Из сообщений» в iOS, подсказки СМС в Android) работают так, как принято на платформе, а программы чтения с экрана видят одно текстовое поле. Вводятся только цифры, не более `length`; Backspace удаляет последнюю цифру, а `onComplete` сообщает, что код введён полностью. Внешний вид определяет приложение: у `OtpInput` нет собственных цветов, размеров, границ и подписи. Его части — ячейки, группы, на которые они разбиты, и каретка, — и приложение стилизует каждую из них.

```rust
use nsh_base::{OtpInputCompleteEvent, otp_input, theme};
use nsh_core::prelude::*;

fn verification(verify: impl Fn(&str) + 'static) -> impl IntoElement {
    let code = signal(SharedString::default());
    otp_input()
        .length(6)
        .groups(2)
        .value(move || Some(code.get()))
        .on_change(move |next: &SharedString| code.set(next.clone()))
        .on_complete(move |e: &OtpInputCompleteEvent| verify(&e.value))
        .accessibility_label("Verification code, 6 digits")
        .flex_row()
        .gap_3()
        .group_style(|s| s.flex_row().gap_2())
        .cell_style(|s| {
            s.w(px(40.0))
                .h(px(48.0))
                .items_center()
                .justify_center()
                .rounded(px(8.0))
                .border_1()
                .border_color(move || theme().input)
                .text_xl()
        })
        // The cell that takes the next digit, while the code has the focus,
        // and the cells that hold a digit.
        .cell_class("focus:border-ring data-filled:bg-muted")
        .caret_style(|s| s.w(px(2.0)).h(px(24.0)).bg(move || theme().foreground))
}
```

```tsx
import { mount, signal } from "nsh";
import { OtpInput } from "nsh/base";

function App() {
  const code = signal("");
  const verify = (value: string) => console.log(`checking ${value}`);
  return (
    <OtpInput
      length={6}
      groups={2}
      value={code()}
      onChange={({ value }) => code.set(value)}
      onComplete={({ value }) => verify(value)}
      accessibilityLabel="Verification code, 6 digits"
      class="flex-row gap-3"
      groupClass="flex-row gap-2"
      cellClass="w-10 h-12 items-center justify-center rounded-lg border border-input text-xl focus:border-ring data-filled:bg-muted"
      caretClass="w-0.5 h-6 bg-foreground"
    />
  );
}

mount(App);
```

`OtpInput` поставляется из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), крейта `nsh-base` для Rust. В Rust `otp_input()` создаёт компонент с сеттером для каждого свойства (`.length(..)`, `.cell_style(..)`), методом-слушателем для каждого события (`.on_complete(..)`) и типизированными методами стилей для самого поля.

<a id="the-code"></a>

## Код

- **Управляемый режим**: передайте `value` и обновляйте его в `onChange`. Ячейки показывают `value`, переданное приложением, как есть; значение, которое задаёт приложение (например, очистка кода после неверного ввода), никогда не передаётся в обработчик.
- **Неуправляемый режим**: передайте `defaultValue` (или ничего) и читайте код из `onChange` и `onComplete`.

Действия пользователя считываются из поля, при этом каретка всегда находится в его конце:

| Пользователь | Код |
|---|---|
| Вводит цифру | Она добавляется в конец, пока код короче `length`; когда код заполнен, ничего не меняется. |
| Вводит полноширинную цифру (`０`–`９`, из метода ввода для восточноазиатских языков) | Добавляется ASCII-цифра. |
| Вводит что-либо другое | Ничего не меняется; поле отбрасывает ввод. |
| Нажимает Backspace | Последняя цифра удаляется. |
| Вставляет текст или система автозаполняет код | Цифры из него добавляются тем же способом; текст, содержащий `length` цифр или больше (`123-456`, код из сообщения), заменяет код своими первыми `length` цифрами. |

`onChange` сообщает о каждом изменении, передавая новый код, а `onComplete` срабатывает следом, если изменение заполнило последнюю ячейку, — именно здесь приложению следует проверять код.

<a id="props"></a>

## Свойства

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `length` | `.length(..)` | `number` | обязательное | Сколько цифр в коде: по одной ячейке на каждую. |
| `value` | `.value(..)` | `string` | нет | Код, которым управляет приложение (показывается как есть). Без него поле хранит код само. |
| `defaultValue` | `.default_value(..)` | `string` | нет | Начальный код, пока не задано `value`. |
| `masked` | `.masked(..)` | `boolean` | `false` | Заполненные ячейки показывают точку вместо цифры, а поле становится защищённым: программы чтения с экрана не зачитывают код, а система не включает его в свои подсказки. |
| `disabled` | `.disabled(..)` | `boolean` | `false` | Нет фокуса и редактирования; стили `disabled:` применяются к полю и его частям, а программы чтения с экрана сообщают, что оно недоступно. |
| `groups` | `.groups(..)` | `number` | `1` | На сколько групп разбиты ячейки, максимально равномерно (3 + 3 для шести цифр в двух группах); не менее одной, не более одной на ячейку. |
| `accessibilityLabel` | `.accessibility_label(..)` | `string` | нет | Что программы чтения с экрана произносят для поля: для чего нужен код и сколько в нём цифр. |
| `cellClass`, `cellStyle` | `.cell_class(..)`, `.cell_style(\|s\| ..)` | `string`, `StyleProp<TextStyle>` | нет | Каждая ячейка: её размер, граница и текст (`color`, `fontSize`, которые наследует цифра); `focus:` и `data-filled:` стилизуют её состояния (см. «Части»). |
| `groupClass`, `groupStyle` | `.group_class(..)`, `.group_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Каждая группа ячеек (`flex-row gap-2`). |
| `caretClass`, `caretStyle` | `.caret_class(..)`, `.caret_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Каретка: её ширина, высота и цвет. |
| `class`, `style` | методы стилей | `string`, `StyleProp<ViewStyle>` | нет | Само поле: строка (или столбец) вокруг его групп. |

<a id="events"></a>

## События

| Событие | Rust | Данные | Когда |
|---|---|---|---|
| `onChange` | `.on_change(\|value: &SharedString\| ..)` | `(e: { value: string })` | Пользователь изменил код: ввёл, удалил, вставил или автозаполнил. |
| `onComplete` | `.on_complete(\|e: &OtpInputCompleteEvent\| ..)` | `{ value }` | Изменение пользователя заполнило последнюю ячейку; срабатывает после `onChange`. |
| `onFocus` / `onBlur` | `.on_focus` / `.on_blur` | нет | Поле получило / потеряло фокус клавиатуры. |

<a id="ref-methods"></a>

## Методы ref

| Метод | Rust | Описание |
|---|---|---|
| `focus()` | `focus()` (`OtpInputApi`) | Передаёт полю фокус клавиатуры и показывает экранную клавиатуру, как при нажатии на поле. |

<a id="parts"></a>

## Части

| Часть | Внутри | Стилизуется с помощью |
|---|---|---|
| Поле | | `class`, `style` |
| Группа | поле, по одной на группу | `groupClass`, `groupStyle`; `disabled:` следует за полем |
| Ячейка | её группа, по одной на цифру | `cellClass`, `cellStyle`; `focus:` у ячейки, которая примет следующую цифру, пока у поля есть фокус, `data-filled:` у каждой ячейки с цифрой, `disabled:` следует за полем |
| Цифра | заполненная ячейка | стили текста ячейки (`text-xl`, `font-semibold`); `•` при `masked` |
| Каретка | пустая ячейка, которая примет следующую цифру, пока у поля есть фокус | `caretClass`, `caretStyle` |

Ячейка, которая принимает следующую цифру, — это ячейка после последней цифры, а когда код заполнен, — последняя ячейка. Каретка мигает так же, как в текстовом поле (полсекунды видна, полсекунды скрыта и остаётся неподвижной на короткое время после каждого изменения), за счёт изменения прозрачности, на нативном уровне, при этом сохраняя своё место в раскладке; при включённом режиме уменьшения движения она не мигает. Нажатие в любом месте поля передаёт ему фокус: текстовое поле лежит поверх ячеек.

Ячейка с цифрой, скрытой или нет, имеет атрибут `data-filled`, поэтому классы `data-filled:` стилизуют ячейки, которые заполнил пользователь (более яркая граница, заливка: `data-filled:border-foreground data-filled:bg-muted`), так же как shadcn/ui записывает свои состояния. При `group` в `cellClass` классы `group-data-filled:` стилизуют содержимое заполненной ячейки — её цифру. Ячейки меняют свои классы, но не содержимое, поэтому нажатие клавиши перестилизует одну ячейку и ничего не перерисовывает заново.

<a id="accessibility"></a>

## Доступность

- Программы чтения с экрана видят одно текстовое поле с именем из `accessibilityLabel` (укажите, для чего нужен код и сколько в нём цифр: «Код подтверждения, 6 цифр»), значением которого служит код; ячейки от них скрыты. Пока `masked`, поле защищённое, поэтому код не зачитывается вслух.
- Клавиатура — платформенная: цифровая на телефонах, любая на компьютерах (вводятся только цифры) и Backspace. Tab переводит фокус на поле, как на любое текстовое поле.
- Пока `disabled`, оно не получает фокус, а программы чтения с экрана сообщают, что оно недоступно.

Общие сведения о подписях, ролях и объявлениях см. в разделе [Доступность](https://nsh.dev/translations/ru/docs/accessibility.md).

<a id="platform-notes"></a>

## Особенности платформ

- iOS: поле является полем для кода (`textContentType` `oneTimeCode`), поэтому панель QuickType предлагает код из «Сообщений» или «Почты»; открывается цифровая клавиатура. Поле `masked` начинается заново, если в него снова вводят текст после потери фокуса, как и защищённые поля iOS.
- Android: `autofillHints` `smsOTPCode` позволяет клавиатуре и службе автозаполнения предлагать код из СМС; открывается цифровая клавиатура.
- macOS, Windows, Linux и веб: набор текста и вставка направляются в поле (в браузере — с `inputmode="numeric"` для цифровой клавиатуры телефона), а над ячейками отображается указатель в виде текстового курсора (I-beam).
