diff --git a/packages/dev/s2-docs/pages/react-aria/TextField.mdx b/packages/dev/s2-docs/pages/react-aria/TextField.mdx index 2641f48bbe3..d58aca35d0c 100644 --- a/packages/dev/s2-docs/pages/react-aria/TextField.mdx +++ b/packages/dev/s2-docs/pages/react-aria/TextField.mdx @@ -8,7 +8,7 @@ import {TextField as TailwindTextField} from 'tailwind-starter/TextField'; import '../../tailwind/tailwind.css'; import Anatomy from '@react-aria/textfield/docs/anatomy.svg'; -export const tags = ['input']; +export const tags = ['input', 'otp']; export const relatedPages = [{'title': 'useTextField', 'url': 'TextField/useTextField.html'}]; export const description = 'Allows a user to enter a plain text value with a keyboard.'; @@ -106,6 +106,153 @@ import {TextField, Label, TextArea} from 'react-aria-components'; ``` +## OTP Input + +TextField can be customized to create an OTP (one-time password) input by hiding the actual input and rendering styled character boxes instead. This uses `InputContext` to access the current value and render each character in a separate box. + + + ```tsx render type="vanilla" + "use client"; + import {TextField, Input, InputContext} from 'react-aria-components'; + import {useState, useContext} from 'react'; + + function OTPInput(props) { + let [focused, setFocused] = useState(false); + let length = props.length ?? 6; + + return ( + + + setFocused(true)} + onBlur={() => setFocused(false)} + inputMode="numeric" + pattern="[0-9]*" + autoComplete="one-time-code" + style={{ + position: 'absolute', + inset: 0, + width: '100%', + height: '100%', + opacity: 0, + pointerEvents: 'auto', + cursor: 'text', + caretColor: 'transparent' + }} + /> + + ); + } + + function OTPBoxes({length, focused}) { + let context = useContext(InputContext); + let value = String(context?.value ?? ''); + + return ( +
+ {Array.from({length}, (_, i) => ( +
= length)) + ? '0 0 0 2px var(--focus-ring-color)' + : 'none', + color: 'var(--field-text-color)', + transition: 'box-shadow 150ms' + }}> + {value[i] ?? ''} +
+ ))} +
+ ); + } + + + ``` + + ```tsx render type="tailwind" + "use client"; + import {TextField, Input, InputContext} from 'react-aria-components'; + import {useState, useContext} from 'react'; + + function OTPInput(props) { + let [focused, setFocused] = useState(false); + let length = props.length ?? 6; + + return ( + + + setFocused(true)} + onBlur={() => setFocused(false)} + inputMode="numeric" + pattern="[0-9]*" + autoComplete="one-time-code" + className="absolute inset-0 w-full h-full + opacity-0 cursor-text caret-transparent" + /> + + ); + } + + function OTPBoxes({length, focused}) { + let context = useContext(InputContext); + let value = String(context?.value ?? ''); + + return ( +
+ {Array.from({length}, (_, i) => { + let isAtCaret = value.length === i; + let isLastFilled = i === length - 1 && value.length >= length; + return ( +
+ {value[i] ?? ''} +
+ ); + })} +
+ ); + } + + + ``` + +
+ ## API