Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

OneTimeCodeField

One input for a one-time code, drawn as a slot per digit, that takes a pasted or autofilled code whole.

import { OneTimeCodeField } from "@butteryui/react";

Demo

The code works for 10 minutes.

length6

When to use it

Use it for

  • The code step of a passwordless sign-in or sign-up, as AuthFlow draws it
  • Confirming an email address or a phone number with a code sent to it
  • A short numeric code someone reads off another screen, such as a locker or pairing code

Not for

  • A password or a code with letters: use PasswordField or TextField
  • A whole sign-in screen: use AuthFlow, which brings the identifier step and the resend timer

Examples

Confirm a phone

Confirming a new phone number in account settings with the code texted to it; 112233 confirms it.

Phone number

We texted a code to (503) 555-0142 to check it's yours.

Four digits

A shorter code for a pickup locker, and the field's look while a code is checked and once it's accepted.

Opening locker 12…

Opening locker 12…

Locker 12 is open

Locker 12 is open

New device

Both looks: an outlined code field on a page, and a soft one in a settings panel that confirms a new device.

The code works for 10 minutes.

Type the six digits it shows.

Pasted code

A code pasted with its dash keeps only its digits, and the field says when it's whole.

Paste the code from your message, dash and all.

0 of 6 digits

Verify code

A code checked as soon as its last digit is in: 246810 signs in, any other code is turned down.

The code works for 10 minutes.

Variants and states

States

Waiting for a code with a note, checking one, accepted, and turned down with what to do next.

The code works for 10 minutes.

Verifying…

Verifying…

Signed in

Signed in

That code isn't right. Check it and try again.

That code isn't right. Check it and try again.

Lengths

A slot for each digit, from a 4-digit locker code to an 8-digit one.

Props

OneTimeCodeField

PropTypeDefaultDescription
labelstring"One-time code"What the field asks for, read with it. Defaults to "One-time code".
hideLabelbooleanfalseKeeps the label for screen readers without showing it, for a step whose title already asks. Defaults to false.
variant"outline" | "soft""outline""outline" (the default), or "soft", a filled box whose edge shows on hover and focus, as every text input takes.
lengthnumber6Digits in the code, one slot each. Defaults to 6.
valuestringThe digits typed so far. Pair with onValueChange to control it.
defaultValuestringThe digits it starts with when uncontrolled.
onValueChange((value: string) => void)Called with the digits on every change; anything else typed or pasted, such as a dash, is left out.
onComplete((code: string) => void)Called once with the code when every slot is filled; filling it with the same code again doesn't call it twice.
status"idle" | "verifying" | "success" | "error""idle"Where the code stands. "error" draws the danger color and takes focus back. Defaults to "idle".
messageReactNodeA note under the field, such as how long the code lasts, or what went wrong while status is "error".
labelsPartial<OneTimeCodeLabels>The words for checking and success.
webOtpbooleanfalseFills in a code from a text message where the browser offers it (the Web OTP API). Defaults to false.

It also takes Omit<ComponentPropsWithRef<"input">, "value" | "defaultValue" | "children" | "size" | "maxLength" | "type" | "inputMode" | "autoComplete">.

OneTimeCodeLabels

The words a `OneTimeCodeField` shows while it checks a code and once it's accepted.

FieldTypeDefaultDescription
verifying

required

string"Verifying…"Under the field while the code is checked. Defaults to "Verifying…".
success

required

string"Signed in"Under the field once the code is accepted. Defaults to "Signed in".

Accessibility

It's one real input with a label ("One-time code" by default, kept for screen readers with hideLabel), numeric and marked autocomplete="one-time-code", so the system's code from a message, a password manager and a paste all fill it at once. The slots are drawn for the eye and hidden from screen readers, which read the input's value. The note or status under the field describes the input, and checking, success and errors are read out as they happen. An error takes focus back to the field.

KeysWhat they do
DigitsFill the next slot; anything else typed is left out
BackspaceClears the digit before the caret
Arrow left and rightMove between slots
EnterSubmits the form it's in

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: The check appears at once and the caret holds still; the border and fill still change color.

Layout

It fills its container's width, its slots sharing it evenly. The box is --bt-h-cta (48px) tall, and the whole box takes presses and pastes. The line under it is kept in every state, so a status coming or going never moves what's below.