# Code Slots

A one-time code field. Each digit springs into its slot as a filled tile, and the caret glides along behind it. Paste or SMS autofill drops the whole code in with a short cascade. Set `status` to `error` and the digits drain out last to first under the danger tint, then the code clears; set it to `success` and the tiles merge into one wash with a check. Click a slot to move the caret there; Backspace, Delete, the arrow keys, Home and End work as in a text field. In the demo, fill all six slots to see the outcome picked in the panel.

<demo-code-slots>



</demo-code-slots>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/code-slots.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import CodeSlots from '~/components/ui/code-slots/CodeSlots.vue';
  import type { CodeSlotsStatus } from '~/components/ui/code-slots/types';

  const code = ref('');
  const status = ref<CodeSlotsStatus>('idle');

  async function verify(value: string) {
    const ok = await fetch('/api/verify', { method: 'POST', body: value }).then(
      (r) => r.ok,
    );
    status.value = ok ? 'success' : 'error';
  }
</script>

<template>
  <CodeSlots
    v-model="code"
    :length="6"
    :status="status"
    @change="status = 'idle'"
    @complete="verify"
  />
</template>
```

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        length
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Number of slots.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        model-value
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Controlled code (<code>
        v-model
      </code>
      
      ). Digits that arrive from outside land with the cascade; digits removed drain.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        default-value
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Uncontrolled initial code, rendered already landed.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        'idle' | 'error' | 'success'
      </code>
    </td>
    
    <td>
      <code>
        'idle'
      </code>
    </td>
    
    <td>
      <code>
        error
      </code>
      
       drains the slots last to first under the danger tint and clears the code; <code>
        success
      </code>
      
       merges the fills into one wash and locks the input.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mask
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Shows a dot instead of each digit.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        caret
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the blinking caret in the active slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Fades the row and ignores input.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-focus
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Focuses the input on mount.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        accent-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f5f5f5'
      </code>
    </td>
    
    <td>
      Fill of a landed digit and the success wash.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ink-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f5f5f5'
      </code>
    </td>
    
    <td>
      Caret, and the tint of the active slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slot-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#27272a'
      </code>
    </td>
    
    <td>
      Surface of an empty slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        digit-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#18181b'
      </code>
    </td>
    
    <td>
      Digit colour on the fill.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        danger-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ff3b30'
      </code>
    </td>
    
    <td>
      Fill and tint colour while <code>
        status
      </code>
      
       is <code>
        error
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slot-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        44
      </code>
    </td>
    
    <td>
      Slot width in px. Height and digit size follow it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      Space between slots in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        12
      </code>
    </td>
    
    <td>
      Corner radius in px, capped at half the slot size.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounce
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.2
      </code>
    </td>
    
    <td>
      Spring overshoot of the landing fill. <code>
        0
      </code>
      
       is critically damped.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        settle
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      Seconds a digit takes to land or drain.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rise
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      Pixels a digit rises into place. <code>
        0
      </code>
      
       fades only.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cascade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Milliseconds between slots when several land at once or drain on reject.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'One-time code'
      </code>
    </td>
    
    <td>
      Accessible name of the input.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        update:modelValue
      </code>
    </td>
    
    <td>
      <code>
        (code: string)
      </code>
    </td>
    
    <td>
      Every edit, for <code>
        v-model
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        change
      </code>
    </td>
    
    <td>
      <code>
        (code: string)
      </code>
    </td>
    
    <td>
      Every edit, including the clear at the end of a reject.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        complete
      </code>
    </td>
    
    <td>
      <code>
        (code: string)
      </code>
    </td>
    
    <td>
      Once, when the last empty slot is filled.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- One real `<input>` sits over the slots with `inputmode="numeric"` and `autocomplete="one-time-code"`, so iOS and Android offer the SMS code, and screen readers see a single text field.
- `aria-invalid` follows the error status, and a polite live region announces "N of 6 digits entered" or "Code accepted".
- With `prefers-reduced-motion: reduce` digits appear without the spring, rise or cascade, and the caret stops blinking.
- Ported from [React Bits](https://reactbits.dev/micro/code-slots) by David Haz.
