Dots

Code Dots

Read-only progress dots for a PIN or passcode, with error and success states.

Enter your passcode

Passcode, 0 of 4 digits entered

Try 2580

"use client"

import * as React from "react"

The CodeDots component shows how many digits of a PIN or passcode are entered, one dot per digit, without showing the digits. Use it on an unlock or confirm screen together with a Keypad. It is not an input: the caller keeps the code and passes the count. When people type a one-time code they can read, such as an SMS code, use InputOTP instead.

Installation

pnpm dlx shadcn@latest add @dots/code-dots

Usage

import { CodeDots } from "@/components/ui/code-dots"
<CodeDots length={4} filled={code.length} />

Features

  • One dot per digit, filled up to filled and clamped to length
  • error turns the dots destructive and shakes them once; success fills every dot
  • An attempt counter that replays the shake and the announcement on each wrong code
  • A polite live status that announces progress, errors and success; the dots themselves are hidden from assistive tech
  • Two sizes and any code length

Progress

Pass the number of digits entered as filled.

Passcode, 0 of 4 digits entered
Passcode, 1 of 4 digits entered
Passcode, 2 of 4 digits entered
Passcode, 3 of 4 digits entered
Passcode, 4 of 4 digits entered
import { CodeDots } from "@/components/ui/code-dots"

export function CodeDotsProgress() {

States

Set state="error" after a wrong code and state="success" once it is accepted.

Passcode, 2 of 4 digits entered
Wrong passcode
Passcode accepted
import { CodeDots } from "@/components/ui/code-dots"

export function CodeDotsStates() {

Attempts

Increase attempt on every rejected code. The shake replays and the error is announced again. Do not change the component's key for this: remounting the live region can swallow the announcement.

Wrong passcode

1 wrong attempt

"use client"

import * as React from "react"
<CodeDots filled={4} state="error" attempt={attempt} />

Sizes

Use size="sm" for a code inside a sheet or dialog.

Passcode, 2 of 4 digits entered
Passcode, 2 of 4 digits entered
import { CodeDots } from "@/components/ui/code-dots"

export function CodeDotsSizes() {

Length

Set length for codes other than four digits, label to say what the code is, and errorMessage or successMessage to match it.

Verification code, 3 of 6 digits entered
Wrong code
PIN set
import { CodeDots } from "@/components/ui/code-dots"

export function CodeDotsLength() {

Accessibility

The dots are aria-hidden. A visually hidden role="status" region reads progress politely instead, for example "Passcode, 2 of 4 digits entered", then errorMessage or successMessage. It stays mounted so every change is announced.

Say what the code is

label defaults to "Passcode". Change it for a card PIN or a verification code, and change errorMessage and successMessage to match.

<CodeDots length={6} filled={3} label="Verification code" errorMessage="Wrong code" />

Pair it with labelled keys

CodeDots takes no input. Pair it with a Keypad, whose keys each have their own accessible name, and show a visible heading such as "Enter your passcode". The demo at the top of this page does exactly that.

Meaning beyond color

The error state uses a destructive color and a shake. Also show the reason in text, such as "Wrong passcode. Try again."

API Reference

CodeDots

PropTypeDefaultDescription
lengthnumber4How many digits the code has.
fillednumber0How many digits are entered. Clamped to 0…length.
state"default" | "error" | "success""default""error" shakes the dots; "success" fills every dot.
size"default" | "sm""default"The dot size.
attemptnumber0Wrong-attempt counter. Increase it to replay the error.
labelstring"Passcode"What the code is, for screen readers.
errorMessagestring"Wrong passcode"Announced when state is "error".
successMessagestring"Passcode accepted"Announced when state is "success".
classNamestring-Additional classes to apply to the root element.
...propsReact.ComponentProps<"div">-Props spread to the root div, except children.