macbetter

Text and fields

Code Field

Enter a one-time code, one character per cell.

Enter the code sent to your iPhone.
import { CodeField } from "macbetter/code-field"
import { Field, FieldDescription, FieldLabel } from "macbetter/field"

export default function Example() {
  return (
    <Field name="code">
      <FieldLabel>Verification code</FieldLabel>
      <CodeField />
      <FieldDescription>Enter the code sent to your iPhone.</FieldDescription>
    </Field>
  )
}

When to use it

Use a code field for a short code sent by text or email: a sign-in code, a two-factor code, a device pairing code. Each character gets its own cell, but it's one input, so a pasted or autofilled code fills every cell at once and a phone can offer the code from a message.

Say where the code was sent in the description. When the code is wrong or expired, mark the Field invalid and say what to do next.

Usage

import { CodeField } from "macbetter/code-field"

length sets how many cells it draws (six by default). onValueCommit runs once every cell is filled, the moment to verify the code.

Groups, letters and errors

groupSize splits the cells into runs with a dash between, like 3 for "123–456". pattern accepts "digits" (the default), "letters", "alphanumeric" or a regular expression, and picks the matching touch keyboard.

That code has expired. Request a new one.
import { CodeField } from "macbetter/code-field"
import { Field, FieldError, FieldLabel } from "macbetter/field"

export default function Example() {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20, maxWidth: "100%" }}>
      <Field name="recovery">
        <FieldLabel>Recovery key</FieldLabel>
        <CodeField length={8} groupSize={4} pattern="alphanumeric" defaultValue="7HQ2VX" />
      </Field>
      <Field name="expired" invalid>
        <FieldLabel>Sign-in code</FieldLabel>
        <CodeField length={6} groupSize={3} defaultValue="482913" />
        <FieldError match>That code has expired. Request a new one.</FieldError>
      </Field>
    </div>
  )
}

To lay the cells out yourself, pass CodeFieldGroup, CodeFieldSlot and CodeFieldSeparator as children.

Platform behavior

  • macOS. Cells look like small text fields: 34 by 42 points, bordered, with 22-point digits. The active cell takes an accent edge and the focus halo.
  • iPadOS and iOS. Gray filled cells, 46 by 58 points with 12-point corners and 28-point semibold digits, as in the system's verification screens. The active cell takes a 2-point accent edge.
  • Every platform. When the row is wider than its container, as with a long code on a phone, the cells narrow together and keep their shape. The empty active cell shows an accent caret that blinks like the system's, and holds still under Reduce Motion. A typed digit settles into its cell with a small spring.

Accessibility

  • It's one native input with autocomplete="one-time-code", so autofill, paste and undo work, and screen readers read it as a single text field.
  • The cells are hidden from assistive technology; the Field label names the input.
  • Arrow keys move between characters, and clicking a cell selects it.

API

CodeField

PropTypeDefaultDescription
lengthnumber6How many characters, one cell each.
groupSizenumber—Splits the cells into runs of this many with a dash between.
size"md" | "lg""md"Cell size.
pattern"digits" | "letters" | "alphanumeric" | RegExp"digits"Which characters it accepts.
valuestring—The code, for a controlled field.
onValueChange(value, details) => void—Runs on typing, paste, undo or a controller.
onValueCommit(value, details) => void—Runs when every cell is filled.
namestring—The name the code submits under.

CodeFieldGroup, CodeFieldSlot (with its index) and CodeFieldSeparator take the props of a div.

Data attributes

AttributeOnValues
data-sizeCodeFieldmd, lg
data-activeSlotThe cell typing goes to, while focused
data-fake-caretSlotThe active cell while it's empty
data-filledRoot, slotPresent when it holds a character
data-completeRootPresent while every cell is filled

CSS variables

VariablemacOSiOSWhat it sets
--mb-code-field-cell-width34px46pxCell width
--mb-code-field-cell-ratio34 / 4246 / 58Cell width to height; cells keep it as they narrow to fit
--mb-code-field-cell-radius8px12pxCell corners
--mb-code-field-cell-bgField whiteTertiary fillCell background
--mb-code-field-font22px medium28px semiboldDigit type