# One-time code input

Six boxes in two groups of three that act as one field, for a code sent by email or text message.

Block: `smbk-one-time-code-input`. Status: proposed. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `error` | on / off | off | The code is wrong or has run out. Draws the danger edge on every cell, kept while focused; the control also carries aria-invalid. |

Parts: `smbk-one-time-code-input__group`, `smbk-one-time-code-input__cell`, `smbk-one-time-code-input__digit`, `smbk-one-time-code-input__control`

## Markup

```html
<div class="smbk-field smbk-field--layout-stacked">
  <label
    class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-field__label"
    id="fieldone-time-code-input-1-label"
    for="fieldone-time-code-input-1"
  >
    <span class="smbk-label__text">Verification code</span>
  </label>
  <span class="smbk-one-time-code-input">
    <span class="smbk-one-time-code-input__group" aria-hidden="true">
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell">
        <span class="smbk-one-time-code-input__digit">4</span>
      </span>
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell">
        <span class="smbk-one-time-code-input__digit">8</span>
      </span>
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell">
        <span class="smbk-one-time-code-input__digit">1</span>
      </span>
    </span>
    <span class="smbk-one-time-code-input__group" aria-hidden="true">
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell" data-active="">
        <span class="smbk-one-time-code-input__digit"></span>
      </span>
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell">
        <span class="smbk-one-time-code-input__digit"></span>
      </span>
      <span class="smbk-input smbk-input--size-lg smbk-one-time-code-input__cell">
        <span class="smbk-one-time-code-input__digit"></span>
      </span>
    </span>
    <input
      class="smbk-one-time-code-input__control"
      type="text"
      inputMode="numeric"
      autoComplete="one-time-code"
      spellCheck="false"
      maxLength="6"
      id="fieldone-time-code-input-1"
      aria-labelledby="fieldone-time-code-input-1-label"
      aria-describedby="fieldone-time-code-input-1-description"
      name="code"
      value="481"
    >
  </span>
  <p
    id="fieldone-time-code-input-1-description"
    class="smbk-field-message smbk-field-message--type-help smbk-field__description"
  >
    <span class="smbk-field-message__text">We sent a six-digit code to ana@example.com. It works for ten minutes.</span>
  </p>
</div>
```

## React

```tsx
import { Field } from './design-system/components/field/field.js';
import { OneTimeCodeInput } from './design-system/components/one-time-code-input/one-time-code-input.js';

<Field label="Verification code" description="We sent a six-digit code to ana@example.com. It works for ten minutes.">
  <OneTimeCodeInput name="code" defaultValue="481" />
</Field>
```

## Stylesheet

```css
/* smbk-one-time-code-input — One-time code input. Figma "One-time code input" 8217:3276 (State: Empty, Focused,
 * Partly filled, Complete, Error, Disabled).
 * Six cells in two groups of three that act as one field. Each cell is the input's well at the large control's
 * height, square, with the large control's corners: its markup carries smbk-input and smbk-input--size-lg, so the
 * input's stylesheet draws the well, its error edge and its disabled and read-only looks, and names the cell marked
 * data-active, which takes the focus edge while the control has focus. One native text control lies over every cell,
 * unseen, and holds the value: what is typed, pasted or filled in by the phone goes there, and the cells show it.
 * This stylesheet only lays the cells out and lays the control over them. */

.smbk-one-time-code-input {
  position: relative;
  display: inline-flex;
  gap: var(--smbk-space-24);
  max-width: 100%;
  vertical-align: top;
}

.smbk-one-time-code-input__group {
  display: flex;
  gap: var(--smbk-space-inline-md);
  min-width: 0;
}

/* A cell is the large well made square, its digit centred. In a space narrower than the six, the cells narrow
   together and keep their height, so the page never scrolls sideways. Two classes, so the input's own size rules,
   whatever order the stylesheets load in, never widen or pad it. */
.smbk-input.smbk-one-time-code-input__cell {
  flex: 0 1 auto;
  width: var(--smbk-size-control-lg);
  min-width: 0;
  padding: 0;
  justify-content: center;
}

.smbk-one-time-code-input__digit {
  font-variant-numeric: tabular-nums;
}

/* The control lies over every cell and takes every press, unseen: the cells draw its value, so its own text, caret,
   selection and the browser's fill colour are never shown. Its text is the cells' own size, so a phone's browser
   never zooms in as it takes focus. */
.smbk-one-time-code-input__control {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  cursor: text;
}

.smbk-one-time-code-input__control:disabled {
  cursor: not-allowed;
}
```

## Tokens it reads

```json
{
  "space": {
    "24": {
      "$type": "dimension",
      "$value": "24px",
      "$description": "Raw space step, 24 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "inline": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      }
    }
  },
  "size": {
    "control": {
      "lg": {
        "$type": "dimension",
        "$value": "{{size.48}}",
        "$description": "Height of a large control: 48 comfortable, 40 compact. A page may use it for its main call to action; apps use md. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    }
  },
  "typography": {
    "body-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    }
  }
}
```

## Purpose

A field for a short code sent by email or text message to prove who someone is: six boxes in two groups of three, one digit in each, so a reader can check the code against the message at a glance. It acts as one field: one value, one name, one place the phone's suggested code goes. For any other short value, such as a postcode or a PIN a person chose themselves, use a plain input.

## Anatomy

- The block, holding the two groups and the control.
- The group part, three cells side by side; a wider gap between the two groups splits the code in half, as it is read.
- The cell part, one box: the input's well at the large control's height, square, with the large control's corners. Its markup carries the input's classes.
- The digit part, the digit a cell shows.
- The control part, one native text control laid unseen over every cell. It holds the code; the cells only show it.

## When to use

- Checking a sign-in, a new email address or a payment with a code the product has just sent.
- Any code of exactly six digits that a person copies from a message.

## When not to use

- A code with letters in it, or of another length. Use an [input](input.md).
- A password or a PIN the person chose. Use the [password field](password-field.md).
- A number someone works out or adjusts. Use the [number field](number-field.md).

## Behavior

Typing a digit fills the active cell and moves to the next. Backspace clears the last digit and goes back a cell, so pressing it again and again empties the cells from the end; on a filled cell picked in the middle it clears that cell's digit, and the digits after it close up, since the code is one value with no gaps. The arrow keys, Home and End move between the cells, and a press picks the cell under it, never one past the next empty cell. On a filled cell, typing replaces its digit. A paste fills every cell from the start with the digits it holds, whatever else it holds, such as spaces or a dash. Anything but a digit is kept out.

The cell where the next digit goes takes the focus edge while the field has focus. In error every cell draws the danger edge, kept while focused. Disabled and read-only cells take the input's disabled and read-only looks.

On a phone the keyboard shows digits, and the phone can offer the code from a message it has just received, which fills every cell. In a space narrower than the six cells, they narrow together and keep their height, so the page never scrolls sideways.

The value is one string of digits. The React component reports it with `onChange` each time it changes, and the whole code with `onComplete` once the sixth digit is in. In a form, the control's `name` sends the code as one value.

## Accessibility

### What it does for you

- It is one native text field, so a screen reader announces one field by its name, with the digits typed so far as its value, and a form, a password manager and the phone's code suggestion all see one field.
- The cells are hidden from assistive technology, since the field already holds their digits.
- Inside a field component it takes the field's label, help and error lines, its required, disabled and read-only states, and marks itself invalid in error.
- It asks for the numeric keyboard and says it takes a one-time code (`autocomplete="one-time-code"`), so the phone can offer the code.

### What you must do

- Give it a name: put it in a field with a label such as "Verification code", or give it an `aria-label`.
- Say under it where the code was sent and how long it lasts, in the field's help line, and how to get a new one.
- Never send the code away, or clear the cells, on a timer the reader cannot stop. If the code runs out, say so in the field's error line and offer a new one.
- In markup written by hand, write the cells with the input's classes at the large size, hide the groups from assistive technology, mark the cell where the next digit goes `data-active`, and write the error modifier on each cell in error. The cells only show the digits; keeping them in step with the control needs script.

### How to test

- Tab to the field: the first empty cell takes the focus edge.
- Type six digits: each fills a cell and moves on. Press Backspace: the last digit clears. Paste a code with a space in it: every cell fills.
- With a screen reader, the field is read once, by its label, with its digits as its value and its help line after.
- On a phone, the keyboard shows digits.
- With the page 320 pixels wide, the cells narrow and nothing scrolls sideways.

### Keyboard

| Key | What it does |
|---|---|
| 0 to 9 | Fills the active cell and moves to the next. |
| Backspace | Clears the last digit and goes back a cell; on a filled cell picked in the middle, clears its digit. |
| Left arrow or Right arrow | Moves to the cell before or after, never past the next empty cell. |
| Home or End | Moves to the first cell, or to the cell after the last digit. |

## Usage rules

### Do

- Put it in a field with a label and a help line saying where the code went.
- Offer a way to get a new code beside it.
- Send the code with `onComplete` only where a wrong code costs nothing; otherwise let the reader press a button.

### Don't

- Use it for a value of any other length or with letters.
- Split the code into six separate fields of your own: a paste, the phone's suggestion and a screen reader all expect one.
