Codes and secrets
Show a short code on one device, type it on another, lay out a long secret, and copy that tells the truth.
import { CodeInput, CodeDisplay, KeyBlock, … } from "@arcadia/ui"CodeDisplay shows a short code on the device that asks; CodeInput takes it on the device that answers. The groups are spaced by a gap, never a character, so a copied code pastes cleanly. One real field sits under the boxes: paste, autofill of one-time codes and undo behave as in any input, and onComplete fires on the last character.
Secrets
KeyBlock lays a long secret out in fixed groups on fixed lines, so no width ever breaks it.
7K2M-QX9P-H4TR-B8WN-3FDZ-M6CJ-Y2VE
Copy
CopyButton writes through copyText and shows the check only when the text really landed.
A product with a native clipboard registers it once with setClipboardBridge; everywhere else the browser's clipboard runs, then a hidden-field copy. clearAfterMs asks a native bridge to clear a secret it still holds.
CodeInput is one labelled text field (autocomplete="one-time-code"), so screen readers and password managers see a normal input; the boxes are decorative. invalid sets aria-invalid and the shake honours reduced motion. CodeDisplay and KeyBlock are named groups. CopyButton announces "Copied" or "Couldn't copy" in a polite live region.
CodeInput
length- numberDefault6
- Number of boxes.
kind- "numeric" | "alphanumeric"Default"numeric"
numerictakes digits only;alphanumerictakes letters and digits, upper-cased.value- string
- Controlled value (characters only, no separators).
defaultValue- stringDefault""
- Initial value when uncontrolled.
onValueChange- (value: string) => void
- Every change, with the characters typed so far.
onComplete- (value: string) => void
- Called once the last box is filled (typed, pasted or autofilled).
invalid- booleanDefaultfalse
- Draws the boxes in danger and, unless reduced motion is on, shakes them once each time it turns true.
disabled- booleanDefaultfalse
autoFocus- booleanDefaultfalse
group- numberDefault3
- Group split: a wider gap after every
groupboxes. 0 for none. labelrequired- string
- The accessible name ("Code from MacBook Air").
name- string
- Form field name.
rootStyle- RootStyle
- Typed StyleX override: layout only.
coderequired- string
- The code, without separators ("482913").
group- numberDefault3
- Characters per visual group (a gap, never a character, separates them).
size- "md" | "lg"Default"lg"
lgfor the one code a screen is about;mdinside a row or a sheet.label- stringDefault"Code"
- What the code is, for assistive tech ("Code"): the group's name, never drawn.
rootStyle- RootStyle
- Typed StyleX override: layout only.
valuerequired- string
- The secret as it is written, groups joined by
-("ABCD-EFGH-…"). perLine- numberDefault4
- Groups per line. Default 4: a 7-group key sits on two lines and never overflows its box.
masked- booleanDefaultfalse
- Draw every character as a dot (a check screen that must not show the key).
labelrequired- string
- What the secret is, for assistive tech ("Recovery Key").
actions- ReactNode
- Actions under the key (Save, Print, Copy).
rootStyle- RootStyle
- Typed StyleX override: layout only.
textrequired- string | (() => string)
- The text written to the clipboard (or a function that produces it at click time).
label- stringDefault"Copy"
- The label at rest.
copiedLabel- stringDefault"Copied"
- The label for COPIED_HOLD_MS after a copy that landed.
failedLabel- stringDefault"Couldn't copy"
- The label after a copy the clipboard refused.
iconOnly- booleanDefaultfalse
- Draw only the glyph (an IconButton named by
label). clipboard- ClipboardWriteOptions
- Clipboard options:
clearAfterMsfor a secret. onCopied- (ok: boolean) => void
- Called after every copy with whether it landed.
variant- "primary" | "secondary" | "outline" | "ghost" | "text" | "danger"Default"secondary"
primaryis the monochrome ink CTA, for the one action that moves the flow forward (send).size- "xs" | "sm" | "md" | "lg"Default"sm"
- Heights 20 / 24 / 28 / 32.
shape- "rounded" | "pill"
tone- "neutral" | "plan"
planis Plan mode's distance blue: a blue CTA onprimary, blue text elsewhere. There is no amber tone: the sodium light is for literal lights (a running edge, a needs-you dot), never a control's fill.iconEnd- IconName
hint- ShortcutSpec | ReactElement
- Trailing key hint, drawn quietly:
"⇧Tab","⏎",["⌘","K"],"mod+k", or aKbdGroup. Visual only (aria-hidden, so the name stays "Allow", not "Allow ⏎"); the keys are exposed asaria-keyshortcutson the button. Binding the key is yours. pressed- boolean
- Toggle state: sets
aria-pressedanddata-pressed. Omit for a plain action button. rootStyle- ButtonStyle
- Typed StyleX override: where the button sits (margins, self-alignment, flex and grid placement, size bounds) and its
--button-*knobs. render- RenderProp<{ disabled: boolean }>
- Replace the rendered element, e.g.
render={<a href="/settings" />}, or a function of props and state. disabled- boolean
- Native disabled: no pointer events, no tooltip, skipped by Tab.
focusableWhenDisabled- boolean
- Keep a disabled control focusable and hoverable (
aria-disabled), so a tooltip can say why. nativeButton- boolean
- Button semantics, inferred from what renders: a
<button>is native, an<a>stays a link, anything else getsrole="button"and key handling.falseforces button semantics on whatever renders (a link acting as a button). className- string
- A product's hook class for its own layout rules. Being retired: new code places a button with
rootStyle; the call sites left are counted by ui.lint (kit-classname) and only go down.
Rest props go to <button> props.