Skip to content
design
Components

One-time code input

A visible numeric field for a code sent or generated for one use, which a phone fills from the message it arrived in.

On this page

Hamble Point Marina Wi-Fi

Sent by text to the number you gave the marina office.

Joining a marina's network that sends a code by text: six boxes over one field, which a phone fills from the message.
Code
import { Button } from "@bosunkit/components/button";
import { Card } from "@bosunkit/components/card";
import { FormField } from "@bosunkit/components/form-field";
import { OneTimeCodeInput } from "@bosunkit/components/one-time-code-input";
import { useState, type Dispatch, type ReactElement, type SetStateAction, type SyntheticEvent } from "react";

export const description: string =
  "Joining a marina's network that sends a code by text: a box for each of the six digits over one field, which a " +
  "phone offers to fill from the message above its keyboard. A code pasted as 482 913 fills all six.";

const CODE_LENGTH: number = 6;

export default function Showcase(): ReactElement {
  const [code, setCode]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
  const [tried, setTried]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const short: boolean = tried && code.length !== CODE_LENGTH;
  return (
    <Card title="Hamble Point Marina Wi-Fi" style={{ width: "100%", maxWidth: 420 }}>
      <form
        style={{ display: "grid", gap: "var(--space-md)" }}
        onSubmit={(event: SyntheticEvent<HTMLFormElement>): void => {
          event.preventDefault();
          setTried(true);
        }}
      >
        <FormField
          label="Verification code"
          hint="Sent by text to the number you gave the marina office."
          error={
            short
              ? `The code has ${String(code.length)} digits. Type all ${String(CODE_LENGTH)} from the message.`
              : undefined
          }
          required
        >
          <OneTimeCodeInput length={CODE_LENGTH} value={code} onValueChange={setCode} />
        </FormField>
        <Button type="submit" variant="primary">
          Join network
        </Button>
      </form>
    </Card>
  );
}

A one-time code input is a visible type="text" field with inputmode="numeric", autocomplete="one-time-code" and pattern="[0-9]*": a phone shows its number pad and offers the code from the message it arrived in above the keyboard, and a password manager offers a code it generates. The code is shown as typed, because the crew copy it off another screen and a masked field hides the digit they mistyped. It is not a password field, so no manager offers to save it as the vessel's login.

The whole code goes in one field. Spaces and dashes a message groups the digits with ("482 913", "482-913") are left out as the crew type or paste, and the field sets no maxlength, which would cut a pasted code before its space is removed. With length (usually 6) the field draws a box for each digit over that one input, the look an SMS code has: each box shows one digit, the box the next digit goes in is marked and carries the focus ring, and the field takes no more digits than the code has. The arrow keys, Home and End move between the boxes and a tap picks one, and a digit typed there takes that box's place; Tab leaves the field in one press, as it leaves any other. The input is what a paste, the phone's offer and a password manager fill and what a screen reader reads; the boxes only draw it. The caller checks the length and says how many digits are missing.

01One input for the whole codeA paste, the phone's offer and a manager's fill each put the whole code in one input. The boxes are drawn over it; an input per digit breaks all three.
02Show the codeThe crew read the code off a phone or a letter and type it once; seeing it is how they check it.
03Count what is missingAn error says how many digits were typed and how many the message holds.

Examples

Any length

From the text the provider sent you.

A code whose length the caller does not fix, such as one a provider sends in four to eight digits: one field, no boxes, any length.
Code
import { FormField } from "@bosunkit/components/form-field";
import { OneTimeCodeInput } from "@bosunkit/components/one-time-code-input";
import type { ReactElement } from "react";

export const description: string =
  "A code whose length the caller does not fix, such as one a provider sends in four to eight digits: one field, no " +
  "boxes, any length.";

export default function AnyLength(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 320 }}>
      <FormField label="Sign-in code" hint="From the text the provider sent you.">
        <OneTimeCodeInput defaultValue="4829" />
      </FormField>
    </div>
  );
}

Outlined

The six-digit code the marina sent by text.
The outlined appearance, as the maritime inputs draw: the field's name on the outline and the hint under it.
Code
import { OneTimeCodeInput } from "@bosunkit/components/one-time-code-input";
import type { ReactElement } from "react";

export const description: string =
  "The outlined appearance, as the maritime inputs draw: the field's name on the outline and the hint under it.";

export default function Outlined(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 360 }}>
      <OneTimeCodeInput
        appearance="outlined"
        label="Verification code"
        hint="The six-digit code the marina sent by text."
      />
    </div>
  );
}

Invalid

A code with a digit missing: the error counts the digits typed and says how many the message holds.
Code
import { OneTimeCodeInput } from "@bosunkit/components/one-time-code-input";
import type { ReactElement } from "react";

export const description: string =
  "A code with a digit missing: the error counts the digits typed and says how many the message holds.";

export default function Invalid(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 360 }}>
      <OneTimeCodeInput
        appearance="outlined"
        label="Verification code"
        defaultValue="48291"
        error="The code has 5 digits. Type all 6 from the message."
      />
    </div>
  );
}

Disabled

A disabled field, while the code is being checked.
Code
import { OneTimeCodeInput } from "@bosunkit/components/one-time-code-input";
import type { ReactElement } from "react";

export const description: string = "A disabled field, while the code is being checked.";

export default function Disabled(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 360 }}>
      <OneTimeCodeInput appearance="outlined" label="Verification code" defaultValue="482913" disabled />
    </div>
  );
}

Props

PropTypeDefaultDescription
valuestring | undefined–The code, when the caller holds it: digits only, as oneTimeCodeDigits reads what was typed.
defaultValuestring | undefined–The code it starts with when the caller does not hold value.
onValueChange((value: string) => void) | undefined–Called with the code's digits as the crew type or paste it, spaces and dashes left out.
appearance"plain" | "outlined" | undefined"plain"plain draws the kit's text input, for a FormField; outlined draws the maritime inputs' outlined field.
labelstring | undefined–The field's name on the outline, with appearance="outlined"; omit it inside a FormField.
hintReactNode–Text under the outlined field, such as where the code was sent; inside a FormField, use its hint.
errorstring | undefined–What is wrong with the code and what to do, with appearance="outlined"; announced when it appears.
namestring | undefined"one-time-code"The form field name; one-time-code by default, so a form's fields keep stable names.
lengthnumber | undefined–How many digits the code has, usually 6. The plain field then draws a box for each over its one input, with the box the next digit goes in marked, and takes no more digits than that; the outlined field keeps its one box and takes no more. Without it the field takes a code of any length, and the caller checks it.
classNamestring | undefined–Classes added to the field's root.
testIdstring | undefined–data-testid on the field.
refRef<HTMLInputElement> | undefined–The input element.
motionMotionProp | undefined–How the outlined field's error line moves; see OutlinedField.

Also takes every attribute of Omit< InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue" | "autoComplete" | "inputMode" | "pattern" | "children" | "size" >.

Do
Draw the code's boxes over one input, so a paste or the phone's offer fills all of them at once.
Don't
Don't make each box an input of its own: a paste fills the first box only, and a screen reader reads six unnamed fields.

Sources: web.dev, SMS OTP form best practices; GOV.UK Design System, Password input; W3C, Understanding SC 3.3.8 Accessible Authentication (Minimum).

  • Password input: for a password the crew keep, masked, with a show button.
  • Form field: the label, hint and error a plain code input sits in.
  • Outlined field: the box the outlined appearance draws in.