InputOTP

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.

Click the first cell and type a code. Backspace sends the goo back.

Customize

Preset
Speed 1.00×
Colour
Flow 1.00×
Pulse 1.00×

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

VariableDefaultWhat it colors
--goop-otp-colorvar(--ring)The focus goo and pulses.
--goop-otp-opacity0.3Their opacity.

Props

otp, type, mask, placeholder, disabled, name, required, dir, and @complete go straight to reka's PinInputRoot.

GoopInputOTP props
PropTypeDefaultNotes
modelValuestring[]The code, one entry per cell (v-model).
defaultValuestring[]The initial code when uncontrolled.
otpbooleantrueautocomplete="one-time-code", so phones offer the code from a text message.
type'text' | 'number''text'number accepts digits only.
classstring | array | objectMerged onto the root, like shadcn.
goobooleantrueMaster switch. false renders plain shadcn cells.
flowboolean | numbertrueWhen focus moves to another cell, the goo gathers, necks out and flows into it.
pulseboolean | numbertrueA digit landing in a cell plops a small, low dollop of goo into it.
floodboolean | numbertrueFocusing from outside floods the cell from where you clicked; leaving drains it. Off: it snaps.
wobbleboolean | numbertrueThe crest of the flood front and a small jelly wobble once the cell is full.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe 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.