Componentes Entradas OTP input OTP input Campo para códigos de un solo uso, con una casilla por carácter.
Navegadores Chrome 123+ , Firefox 121+ , Safari 17.5+ Nivel 2 Tamaño comprimido 1.53 kB Presupuesto de diseño forzado 0 ms Presupuesto de axe 0 Compatible con SSR Sí Contrato publicado Entorno de pruebas Zona de pruebas 1 2 3 4 5 6 7 8 9 10 11 12 import { OtpInput } from " @gunkin/uiify " ;
export default function Example ( ) {
return (
< OtpInput . Root className = " " id = " verification-code " length = { 6 } >
{ Array . from ( { length : 6 } , (_, index) = > (
< OtpInput . Slot className = " " index = { index } key = { index } />
)) }
< OtpInput . HiddenInput />
</ OtpInput . Root >
) ;
} Show code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
@ layer components {
[ data-uiify-otp-input ] {
position : relative ;
display : inline-flex ;
gap : var ( --space-sm ) ;
cursor : text ;
}
[ data-uiify-otp-input ] [ data-part = "slot" ] {
display : inline-flex ;
align-items : center ;
justify-content : center ;
inline-size : 2 . 5rem ;
block-size : 2 . 5rem ;
border : var ( --border-width ) solid var ( --input ) ;
border-radius : var ( --radius-md ) ;
corner-shape : squircle ;
background-color : var ( --card ) ;
color : var ( --foreground ) ;
font-size : var ( --text-lg ) ;
font-weight : var ( --weight-medium ) ;
transition : var ( --transition-colors ) ;
}
[ data-uiify-otp-input ] [ data-part = "slot" ] [ data-active ] {
border-color : var ( --primary ) ;
outline : 2px solid color-mix ( in oklch , var ( --ring ) 35 % , transparent ) ;
outline-offset : 0 ;
}
[ data-uiify-otp-input ] [ data-part = "slot" ] [ data-active ] : : after {
inline-size : 1px ;
block-size : 1em ;
background-color : currentcolor ;
content : "" ;
animation : uiify-otp-input-caret-blink 1s step-end infinite ;
}
@ keyframes uiify-otp-input-caret-blink {
50 % {
opacity : 0 ;
}
}
@ media ( prefers-reduced-motion : reduce ) {
[ data-uiify-otp-input ] [ data-part = "slot" ] [ data-active ] : : after {
animation : none ;
}
}
[ data-uiify-otp-input ] [ data-part = "slot" ] [ data-filled ] {
border-color : var ( --input ) ;
}
[ data-uiify-otp-input ] [ data-disabled ] [ data-part = "slot" ] {
opacity : 0 . 5 ;
cursor : not-allowed ;
}
}
@ layer components {
[ data-uiify-otp-input ] : not ( [ data-disabled ] ) {
cursor : text ;
}
} Show code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import { OtpInput } from " @gunkin/uiify " ;
export default function Example ( ) {
return (
< OtpInput . Root
className = "
relative inline-flex gap-sm [cursor:text] [&_[data-part=slot]]:inline-flex [&_[data-part=slot]]:items-center
[&_[data-part=slot]]:justify-center [&_[data-part=slot]]:[inline-size:2.5rem]
[&_[data-part=slot]]:[block-size:2.5rem] [&_[data-part=slot]]:border-[length:var(--border-width)]
[&_[data-part=slot]]:border-solid [&_[data-part=slot]]:border-input [&_[data-part=slot]]:rounded-md
[&_[data-part=slot]]:[corner-shape:squircle] [&_[data-part=slot]]:bg-card [&_[data-part=slot]]:text-foreground
[&_[data-part=slot]]:text-lg [&_[data-part=slot]]:font-[number:var(--weight-medium)]
[&_[data-part=slot]]:[transition:var(--transition-colors)] [&_[data-part=slot][data-active]]:border-primary
[&_[data-part=slot][data-active]]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)]
[&_[data-part=slot][data-active]]:[outline-offset:0] [&_[data-part=slot][data-active]::after]:[inline-size:1px]
[&_[data-part=slot][data-active]::after]:[block-size:1em] [&_[data-part=slot][data-active]::after]:bg-current
[&_[data-part=slot][data-active]::after]:content-[ ' ' ]
[&_[data-part=slot][data-active]::after]:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite]
motion-reduce:[&_[data-part=slot][data-active]::after]:[animation:none]
[&_[data-part=slot][data-filled]]:border-input [&[data-disabled]_[data-part=slot]]:opacity-50
[&[data-disabled]_[data-part=slot]]:cursor-not-allowed [&:not([data-disabled])]:[cursor:text]
"
id = " verification-code "
length = { 6 }
>
{ Array . from ( { length : 6 } , (_, index) = > (
< OtpInput . Slot
className = "
grid place-items-center size-10 border-[length:var(--border-width)] border-solid border-input rounded-md
bg-card text-foreground text-lg font-medium tabular-nums [transition:var(--transition-colors)]
data-[active]:border-primary
data-[active]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)]
data-[active]:[outline-offset:0] data-[active]:after:content-[ ' ' ] data-[active]:after:[inline-size:1px]
data-[active]:after:[block-size:1em] data-[active]:after:bg-current
data-[active]:after:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite]
motion-reduce:data-[active]:after:[animation:none] data-[filled]:border-input data-[disabled]:opacity-50
data-[disabled]:cursor-not-allowed
"
index = { index }
key = { index }
/>
)) }
< OtpInput . HiddenInput />
</ OtpInput . Root >
) ;
} Show code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
@ layer reset , ui , app , tokens , base , demo , components , utilities , theme ;
@ import "@gunkin/uiify/styles/reset" ;
@ import "@gunkin/uiify/components/behavior.css" ;
@ import "@gunkin/uiify/styles/tokens" ;
@ import "@gunkin/uiify/styles/tailwind" ;
@ import "tailwindcss/utilities.css" ;
@ theme { --spacing : 0 . 25rem ; --font-weight-normal : 400 ; --font-weight-medium : 500 ; --font-weight-semibold : 600 ; --font-weight-bold : 700 ; }
@ source inline ( 'relative inline-flex gap-sm [cursor:text] [&_[data-part=slot]]:inline-flex [&_[data-part=slot]]:items-center [&_[data-part=slot]]:justify-center [&_[data-part=slot]]:[inline-size:2.5rem] [&_[data-part=slot]]:[block-size:2.5rem] [&_[data-part=slot]]:border-[length:var(--border-width)] [&_[data-part=slot]]:border-solid [&_[data-part=slot]]:border-input [&_[data-part=slot]]:rounded-md [&_[data-part=slot]]:[corner-shape:squircle] [&_[data-part=slot]]:bg-card [&_[data-part=slot]]:text-foreground [&_[data-part=slot]]:text-lg [&_[data-part=slot]]:font-[number:var(--weight-medium)] [&_[data-part=slot]]:[transition:var(--transition-colors)] [&_[data-part=slot][data-active]]:border-primary [&_[data-part=slot][data-active]]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)] [&_[data-part=slot][data-active]]:[outline-offset:0] [&_[data-part=slot][data-active]::after]:[inline-size:1px] [&_[data-part=slot][data-active]::after]:[block-size:1em] [&_[data-part=slot][data-active]::after]:bg-current [&_[data-part=slot][data-active]::after]:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite] motion-reduce:[&_[data-part=slot][data-active]::after]:[animation:none] [&_[data-part=slot][data-filled]]:border-input [&[data-disabled]_[data-part=slot]]:opacity-50 [&[data-disabled]_[data-part=slot]]:cursor-not-allowed [&:not([data-disabled])]:[cursor:text] grid place-items-center size-10 border-[length:var(--border-width)] border-solid border-input rounded-md bg-card text-foreground text-lg font-medium tabular-nums [transition:var(--transition-colors)] data-[active]:border-primary data-[active]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)] data-[active]:[outline-offset:0] data-[active]:after:[inline-size:1px] data-[active]:after:[block-size:1em] data-[active]:after:bg-current data-[active]:after:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite] motion-reduce:data-[active]:after:[animation:none] data-[filled]:border-input data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed' ) ;
@ source inline ( "[&_[data-part=slot][data-active]::after]:content-[''] data-[active]:after:content-['']" ) ;
@ layer base { : root { --uiify-control-text : var ( --text-sm ) ; } @ media ( pointer : coarse ) { : root { --uiify-control-text : var ( --text-base ) ; } } }
@ layer components {
@ keyframes uiify-presence-in {
from {
opacity : 0 ;
scale : var ( --presence-scale ) ;
}
}
}
@ layer components {
@ keyframes uiify-presence-out {
to {
opacity : 0 ;
scale : var ( --presence-scale ) ;
}
}
}
@ property --uiify-spinner-tail {
syntax : "<angle>" ;
inherits : false ;
initial-value : 0deg ;
}
@ property --uiify-spinner-length {
syntax : "<angle>" ;
inherits : false ;
initial-value : 36deg ;
}
@ layer components {
@ keyframes uiify-spinner-rotate {
to {
transform : rotate ( 360deg ) ;
}
}
}
@ layer components {
@ keyframes uiify-spinner-orbit {
0 % {
--uiify-spinner-tail : 0deg ;
--uiify-spinner-length : 36deg ;
}
50 % {
--uiify-spinner-tail : 90deg ;
--uiify-spinner-length : 324deg ;
}
100 % {
--uiify-spinner-tail : 360deg ;
--uiify-spinner-length : 36deg ;
}
}
}
@ layer components {
@ keyframes uiify-skeleton-pulse {
50 % {
opacity : 0 . 45 ;
}
}
}
@ layer components {
@ keyframes uiify-skeleton-shimmer {
to {
background-position : - 200 % 0 ;
}
}
}
@ layer components {
@ keyframes uiify-skeleton-reveal {
from {
opacity : 0 ;
}
to {
opacity : 1 ;
}
}
}
@ property --candle-body-height {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 0 % ;
}
@ property --candle-body-top {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 50 % ;
}
@ property --candle-slot {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 100 % ;
}
@ property --candle-wick-height {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 0 % ;
}
@ property --candle-wick-top {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 50 % ;
}
@ property --candle-x {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 0 % ;
}
@ property --candle-scale {
syntax : "<number>" ;
inherits : true ;
initial-value : 1 ;
}
@ property --line-y {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 50 % ;
}
@ property --line-prev-y {
syntax : "<percentage>" ;
inherits : true ;
initial-value : 50 % ;
}
@ property --line-count {
syntax : "<integer>" ;
inherits : false ;
initial-value : 1 ;
}
@ property --line-scale {
syntax : "<number>" ;
inherits : true ;
initial-value : 1 ;
}
@ layer components {
@ keyframes uiify-otp-input-caret-blink {
50 % {
opacity : 0 ;
}
}
} Show code
API OtpInput.Rootvaluestring— Código actual; hace que el campo sea controlado. defaultValuestring""Código inicial del campo no controlado. onValueChange(value: string) => void— Se llama con el código cada vez que cambia. onComplete(value: string) => void— Se llama cuando el código alcanza la longitud configurada. lengthnumber6Número de caracteres del código. type"numeric" | "alphanumeric""numeric"Caracteres que se conservan al escribir o pegar. maskbooleanfalseOculta los caracteres introducidos en las casillas. disabledbooleanfalseDesactiva el input oculto y establece el atributo data-disabled. autoFocusbooleanfalseDa el foco al input oculto cuando se monta el campo. idstring— Id aplicado al elemento raíz renderizado. renderReactElement | ((props, state) => ReactElement)— Reemplaza el elemento renderizado y conserva las conexiones de comportamiento y accesibilidad.
OtpInput.Slotindex* number— Posición del código que muestra esta casilla. renderReactElement | ((props, state) => ReactElement)— Reemplaza el elemento renderizado y conserva las conexiones de comportamiento y accesibilidad.
OtpInput.HiddenInputautoFocusboolean— Da el foco a este input al montarlo, reemplazando la configuración de la raíz. renderReactElement | ((props, state) => ReactElement)— Reemplaza el elemento renderizado y conserva las conexiones de comportamiento y accesibilidad.
Teclado Tecla Acción Tab Llevar el foco al control o sacarlo de él Enter / Space Activar cuando tiene el foco (controles de tipo botón)
Accesibilidad Usa HTML semántico siempre que es posible. Inspecciona el marcado renderizado en el entorno de pruebas: ARIA se añade solo cuando los elementos nativos no bastan.
Puntos de estilo data-disabled