@rozie-ui/otp-react
v0.1.6
Published
Idiomatic React headless WAI-ARIA one-time-code / PIN input (segmented cells, paste-to-distribute, keyboard nav, SMS autofill, masking) — one accessible Rozie source compiled to React.
Maintainers
Readme
@rozie-ui/otp-react
Idiomatic react Otp — a headless, fully-accessible (WAI-ARIA) one-time-code / PIN input (segmented native cells, paste-to-distribute, full keyboard navigation, autocomplete="one-time-code" SMS autofill, and optional masking) compiled from one Rozie source. The interaction engine IS the browser's native <input> cells; every visual value is a CSS custom property, so it re-skins to any design system. This package is generated; do not edit src/ by hand.
Install
npm i @rozie-ui/otp-reactPeer dependencies: react + react-dom. Install them alongside this package.
Also installed: @rozie/runtime-react — Rozie's small, tree-shaken runtime helper package (controllable state, keyboard navigation, event modifiers, and safe interpolation). It arrives as a regular dependency, so npm pulls it for you. Your bundler keeps only the helpers this component actually uses — typically a few hundred bytes to a few KB, minified and gzipped. What's in it and what it costs.
Usage
import { useState } from 'react';
import { Otp } from '@rozie-ui/otp-react';
export function Demo() {
const [code, setCode] = useState<string>('');
return (
<Otp
value={code}
onValueChange={setCode}
length={6}
type="numeric"
ariaLabel="Verification code"
onComplete={(e) => console.log('code complete:', e.value)}
/>
);
}
// Masked (password dots) — for sensitive codes.
export function PinDemo() {
const [pin, setPin] = useState<string>('');
return <Otp value={pin} onValueChange={setPin} length={4} mask ariaLabel="PIN" />;
}Theming
Every visual value is a --rozie-otp-* CSS custom property — override any of them at any ancestor scope. Ready-made design-system bridges ship in the package:
import '@rozie-ui/otp-react/themes/shadcn.css'; // or material.css, bootstrap.css, base.cssProps
| Name | Type | Default | Two-way (model) | Required |
| --- | --- | --- | :---: | :---: |
| value | String | '' | ✓ | |
| length | Number | 6 | | |
| type | String | "numeric" | | |
| mask | Boolean | false | | |
| autoFocus | Boolean | false | | |
| disabled | Boolean | false | | |
| placeholder | String | '' | | |
| ariaLabel | String | null | | |
Events
| Event | Description |
| --- | --- |
| change | Fired on every edit (type, paste, backspace, or a programmatic clear) that actually changes the code — a write that produces the same value does not re-emit. Payload { value } — the new contiguous code string (0..length chars). Funneled through one commitValue wrapper so the React prop-destructure hoists exactly once. |
| complete | Fired on the not-full → full transition, i.e. the code reaches length characters. Editing a cell of an already-complete code does not re-fire it, and clear() never fires it. Payload { value } — the complete code string. Use it to auto-submit a verification flow. |
Imperative handle
Beyond props, the component exposes imperative methods (declared once in the Rozie source via $expose). Grab a handle with the native ref mechanism and call them directly. Note: focus() deliberately overrides the inherited HTMLElement.focus (it focuses the first empty cell) — on the Lit custom element this is an accepted ROZ137 warn-only override, the public focus() handle is intended:
| Method | Description |
| --- | --- |
| focus | Move DOM focus to the first empty cell (clamped to the last cell when the code is full). NOTE: this deliberately overrides the inherited HTMLElement.focus on the Lit custom element (ROZ137 warns, warn-only) — the public focus() handle is intended. |
| clear | Reset the code to the empty string (emits change with { value: "" }) and move focus to the first cell. |
import { useRef } from 'react';
import { Otp, type OtpHandle } from '@rozie-ui/otp-react';
const otp = useRef<OtpHandle>(null);
// <Otp ref={otp} ... />
otp.current?.focus();
otp.current?.clear();Slots
This component declares no slots.
