# Voice Pill

> Mic button that swells into a capsule of live equalizer bars and a timer while recording.

A dictation button. Press it and the circle opens to the left into a capsule with a scrolling waveform and an elapsed clock, and the mic turns into a stop mark. A tap latches it on; a long press stops when you let go. While holding, slide left to drag the contents along and reveal Cancel — cross the distance and it stops without a result.

<demo-voice-pill>



</demo-voice-pill>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import VoicePill from '~/components/ui/voice-pill/VoicePill.vue';
  import type { VoicePillStopDetail } from '~/components/ui/voice-pill/types';

  function onStop(detail: VoicePillStopDetail) {
    if (detail.reason !== 'cancel') console.log('recorded', detail.duration);
  }
</script>

<template>
  <VoicePill :size="40" :reach="12" reactive="mic" @stop="onStop" />
</template>
```

The capsule grows to the left of the button, so give it room on that side (for example, place it at the right end of a prompt input).

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        accent-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f5f5f5'
      </code>
    </td>
    
    <td>
      The waveform, the clock and the stop mark.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#a1a1aa'
      </code>
    </td>
    
    <td>
      The mic at rest, and the hover wash.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        background
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#27272a'
      </code>
    </td>
    
    <td>
      The capsule surface.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        28
      </code>
    </td>
    
    <td>
      Footprint in px. The icon, the stop mark and the clock scale with it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shape
      </code>
    </td>
    
    <td>
      <code>
        'pill' | 'rounded'
      </code>
    </td>
    
    <td>
      <code>
        'pill'
      </code>
    </td>
    
    <td>
      A circle that opens into a capsule, or a rounded square.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reach
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      The room the capsule leaves before the waveform, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        show-time
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      An elapsed clock in the capsule, which grows to the left to hold it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        waveform
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      A history of the level scrolling through the capsule, past the clock.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slide-to-cancel
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      While held, sliding left drags the capsule contents along and reveals Cancel.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cancel-distance
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        64
      </code>
    </td>
    
    <td>
      How far left a held pointer slides before it cancels, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        attack
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        40
      </code>
    </td>
    
    <td>
      How fast the level rises to a louder signal, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        release
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        240
      </code>
    </td>
    
    <td>
      How long the level hangs after the sound drops, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sensitivity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Gain on the signal before the envelope.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        floor
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.1
      </code>
    </td>
    
    <td>
      Waveform bar height when silent, as a fraction of its box.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        open-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      The capsule open and close, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        press-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.95
      </code>
    </td>
    
    <td>
      Scale of the button while a pointer is down.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mode
      </code>
    </td>
    
    <td>
      <code>
        'auto' | 'hold' | 'toggle'
      </code>
    </td>
    
    <td>
      <code>
        'auto'
      </code>
    </td>
    
    <td>
      Auto: a tap latches, a hold stops on release. Hold: release always stops. Toggle: release never stops.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hold-after
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        300
      </code>
    </td>
    
    <td>
      In auto, the press length after which a release stops instead of latching, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reactive
      </code>
    </td>
    
    <td>
      <code>
        'simulated' | 'mic'
      </code>
    </td>
    
    <td>
      <code>
        'simulated'
      </code>
    </td>
    
    <td>
      What drives the level. Mic asks for microphone permission on the first press and stops if it is refused.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Dimmed and inert. A listening pill stops.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Dictate'
      </code>
    </td>
    
    <td>
      Accessible name. The state is carried by <code>
        aria-pressed
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the button.
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        start
      </code>
    </td>
    
    <td>
      <code>
        { source: 'simulated' | 'mic' }
      </code>
    </td>
    
    <td>
      Listening began, with the requested source.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stop
      </code>
    </td>
    
    <td>
      <code>
        { reason: VoicePillStopReason, duration: number }
      </code>
    </td>
    
    <td>
      Listening ended. Reason is release, tap, key, escape, blur, cancel, disabled, mic-denied or unmount; duration in ms.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- A native `<button>` with `aria-pressed` reflecting the listening state; the name comes from `aria-label`.
- Space or Enter toggles listening; Escape stops it. Keyboard toggles skip the open/close transition.
- The waveform, clock and Cancel hint are decorative (`aria-hidden`).
- The hit area extends to at least 44px for small sizes.
- With reduced motion, the press scale, blur and slide transitions are removed; only opacity fades remain.
- Ported from [React Bits](https://reactbits.dev/micro/voice-pill) by David Haz.
