Componentes Campos OTP input OTP input Campo para códigos de uso único, com um espaço por caractere.
Navegadores Chrome 123+ , Firefox 121+ , Safari 17.5+ Nível 2 Tamanho compactado 1.53 kB Limite de layout forçado 0 ms Limite do axe 0 Compatível com SSR Sim Contrato publicado Ambiente de teste Experimentar 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 atual, tornando o campo controlado. defaultValuestring""Código inicial do campo não controlado. onValueChange(value: string) => void— Chamado com o código sempre que ele muda. onComplete(value: string) => void— Chamado quando o código atinge o comprimento configurado. lengthnumber6Número de caracteres do código. type"numeric" | "alphanumeric""numeric"Caracteres mantidos ao digitar ou colar. maskbooleanfalseOculta os caracteres inseridos nos espaços. disabledbooleanfalseDesabilita o input oculto e define o atributo data-disabled. autoFocusbooleanfalseFoca o input oculto quando o campo é montado. idstring— Id aplicado ao elemento raiz renderizado. renderReactElement | ((props, state) => ReactElement)— Substitua o elemento renderizado preservando comportamento e conexões de acessibilidade.
OtpInput.Slotindex* number— Posição do código exibida neste espaço. renderReactElement | ((props, state) => ReactElement)— Substitua o elemento renderizado preservando comportamento e conexões de acessibilidade.
OtpInput.HiddenInputautoFocusboolean— Foca este input na montagem, substituindo a configuração da raiz. renderReactElement | ((props, state) => ReactElement)— Substitua o elemento renderizado preservando comportamento e conexões de acessibilidade.
Teclado Tecla Ação Tab Mova o foco para o controle ou para fora dele Enter / Space Ative quando estiver em foco (controles semelhantes a botões)
Acessibilidade Usa HTML semântico sempre que possível. Inspecione a marcação renderizada no ambiente interativo — ARIA só é adicionado quando os elementos nativos não bastam.
Pontos de integração de estilos data-disabled