Components
InputOTP New · experimental
shadcn's one-time-code input where the focus is a pool of goo. It flows from cell to cell as you type, back on Backspace, with a small pulse as each digit lands.
Customize
GoopInputOTP has shadcn-vue's InputOTP parts and classes (GoopInputOTPGroup, GoopInputOTPSlot with its index, GoopInputOTPSeparator), built on reka-ui's PinInput, as shadcn-vue's PinInput is. Each cell is a real <input>, and v-model is an array with one entry per cell. The goo is one aria-hidden layer behind the cells.
Installation
Install @goopui/nuxt or @goopui/vue as shown on the Button page. The Nuxt module auto-imports every part.
Usage
<script setup lang="ts">
// Nuxt auto-imports these. In plain Vue, import them:
import { GoopInputOTP, GoopInputOTPGroup, GoopInputOTPSeparator, GoopInputOTPSlot } from '@goopui/vue'
const code = ref<string[]>([])
</script>
<template>
<GoopInputOTP v-model="code" aria-label="Verification code" @complete="verify">
<GoopInputOTPGroup>
<GoopInputOTPSlot v-for="i in 3" :key="i" :index="i - 1" />
</GoopInputOTPGroup>
<GoopInputOTPSeparator />
<GoopInputOTPGroup>
<GoopInputOTPSlot v-for="i in 3" :key="i" :index="i + 2" />
</GoopInputOTPGroup>
</GoopInputOTP>
</template>
What it does
The focused cell holds a pool of goo, and that pool is the focus indicator. Each part is an effect you can turn off:
- flow. When focus moves to another cell (typing moves it forward, Backspace moves it back), the goo gathers, necks out of the old cell, pinches off, and floods the new one from the side it arrived on, like ToggleGroup's fill moving between items.
- pulse. A digit landing in a cell plops a small, low dollop of goo into it as the pool flows on.
- flood. Focusing a cell from outside floods it from where you clicked; focus leaving the code drains it.
- wobble. The flood front's crest and a small jelly wobble once the cell is full.
Effects and presets
Every effect is a prop. false turns it off, true uses the preset's strength, and a number scales it.
<GoopInputOTP preset="wild" type="number">...</GoopInputOTP>
<GoopInputOTP :flow="false" :pulse="false" type="number">...</GoopInputOTP>
Disabled and goo off
<GoopInputOTP disabled :default-value="['1', '2', '3', '4']">...</GoopInputOTP>
<GoopInputOTP :goo="false">...</GoopInputOTP>
Colors
| Variable | Default | What it colors |
|---|---|---|
--goop-otp-color | var(--ring) | The focus goo and pulses. |
--goop-otp-opacity | 0.3 | Their opacity. |
Props
otp, type, mask, placeholder, disabled, name, required, dir, and @complete go straight to reka's PinInputRoot.
| Prop | Type | Default | Notes |
|---|---|---|---|
modelValue | string[] | The code, one entry per cell (v-model). | |
defaultValue | string[] | The initial code when uncontrolled. | |
otp | boolean | true | autocomplete="one-time-code", so phones offer the code from a text message. |
type | 'text' | 'number' | 'text' | number accepts digits only. |
class | string | array | object | Merged onto the root, like shadcn. | |
goo | boolean | true | Master switch. false renders plain shadcn cells. |
flow | boolean | number | true | When focus moves to another cell, the goo gathers, necks out and flows into it. |
pulse | boolean | number | true | A digit landing in a cell plops a small, low dollop of goo into it. |
flood | boolean | number | true | Focusing from outside floods the cell from where you clicked; leaving drains it. Off: it snaps. |
wobble | boolean | number | true | The crest of the flood front and a small jelly wobble once the cell is full. |
speed | number | 1 | Animation speed multiplier. |
preset | 'subtle' | 'playful' | 'wild' | app config, then playful | The feel: flood and flow timing, pulse size, wobble strength. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn. |
GoopInputOTP exposes handle (the live goo handle, null before mount or with goo off) and el.
Accessibility
Each cell is a real <input> with its own aria-label from reka, and otp sets autocomplete="one-time-code" so phones can fill the code from a text message. Pasting a whole code fills every cell. With the goo attached, the focused cell keeps shadcn's ring-colored border, and the goo replaces the box-shadow ring, so the focus is always visible, keyboard included.
Under reduced motion the focused cell shows a still fill: no flow, flood, wobble, or pulse. See Accessibility & reduced motion.