---
title: "VoiceBeam"
description: "An audio-reactive glow along the bottom edge of the element it wraps."
category: Effects
status: beta
since: 0.6.2
tags: [voice, audio, glow, mic, dictation, waveform]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
Wrap exactly one element that carries its own radius; feed it a microphone `stream`, or drive it with `level`.
:::TuffDemoWrapper{demo="VoiceBeamShowcaseDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const colorMode = useColorMode()
  const beamTheme = computed(() => (colorMode.value === 'dark' ? 'dark' : 'light'))

  // Without a microphone, drive the beam with a per-frame level getter.
  const level = ref(0.5)
  let raf = 0
  let t = 0
  function tick() {
    t += 0.016
    level.value = 0.35 + 0.32 * Math.abs(Math.sin(t * 1.7))
    raf = requestAnimationFrame(tick)
  }
  raf = requestAnimationFrame(tick)
  onBeforeUnmount(() => cancelAnimationFrame(raf))
  </script>

  <template>
    <TxVoiceBeam :theme="beamTheme" :level="() => level" :border-radius="16">
      <TxCard :radius="16" :padding="24" shadow="none">Ask anything…</TxCard>
    </TxVoiceBeam>

    <TxVoiceBeam :theme="beamTheme" :level="() => level" processing :border-radius="16">
      <TxCard :radius="16" :padding="24" shadow="none">Transcribing…</TxCard>
    </TxVoiceBeam>
  </template>
---
:::

### Microphone Input
`useMicrophone()` requests `getUserMedia` with echo cancellation, noise suppression, and auto gain off; call `start()` from a click.

```vue
<script setup lang="ts">
import { useMicrophone } from '@talex-touch/tuffex/pro'

const mic = useMicrophone()
const live = computed(() => mic.state.value === 'live')
</script>

<template>
  <TxVoiceBeam :stream="mic.stream.value" :processing="transcribing">
    <TxCard :radius="16" :padding="24" shadow="none">Ask anything…</TxCard>
  </TxVoiceBeam>
  <TxButton :aria-pressed="live" @click="live ? mic.stop() : mic.start()">
    {{ live ? 'Stop' : 'Listen' }}
  </TxButton>
</template>
```

### Host Presets and Palettes
`type` picks the host preset, and any geometry prop you pass wins over it; `colorVariant` picks the palette.

```vue
<template>
  <TxVoiceBeam type="pill" color-variant="ocean" :scale="0.9">
    <div class="pill">Recording…</div>
  </TxVoiceBeam>

  <TxVoiceBeam type="mobile" color-variant="candy" :level="() => 0.8">
    <div class="screen">Listening</div>
  </TxVoiceBeam>
</template>
```

### Best Practices

- Never let the glow be the only sign the mic is live: keep a text status and the button's `aria-pressed`, and announce changes with `role="status"`.
- Pass `level` a getter (`:level="() => meter.value"`), not a reactive number; the getter is sampled once per frame with no re-render.
- Put content that must stay crisp at `position: relative; z-index: 5`; overlays inside the wrapped element get clipped, so portal them out.
- Call `mic.stop()` when the voice UI closes; the composable stops tracks only on unmount.
- Keep instances few: each runs blurred layers and a canvas, so it doesn't suit dense lists.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `type` | `'default' \| 'pill' \| 'mobile'` | `'default'` | Host preset (chat input, recording pill, phone bottom) that seeds the geometry props. |
| `stream` | `MediaStream \| null` | `null` | Live audio to react to; wins over `level`. |
| `level` | `number \| () => number` | `0` | Manual level (0-1) used when there is no stream. |
| `sensitivity` | `number` | `3.1` | Input gain on the analysed audio. |
| `threshold` | `number` | `0.015` | Noise gate (0-1); levels below it read as silence. |
| `attack` | `number` | `0.325` | Seconds the glow takes to rise. |
| `release` | `number` | `0.86` | Seconds the glow takes to settle. |
| `idle` | `number` | `0.23` | Resting presence (0-1), so the beam never looks dead. |
| `breatheDuration` | `number` | `5.2` | Idle breathing period in seconds. |
| `reach` | `number` | `1.2` | How tall the glow grows at full level. |
| `spread` | `number` | `1.05` | How far the glow widens at full level. |
| `bands` | `boolean` | `true` | Lets low, mid, and high bands move the lobes independently. |
| `flow` | `number` | `48` | Sideways travel of the spectrum in px/s at full level. |
| `processing` | `boolean` | `false` | Gathers the glow into a travelling beam and holds it lit. |
| `processingDuration` | `number` | `1.1` | Seconds for one pass of the processing beam. |
| `processingLevel` | `number` | `0.55` | How lit the glow stays while processing. |
| `processingEase` | `number` | `0.6` | Seconds of the morph in either direction. |
| `processingTravel` | `number` | `1.55` | How far the processing beam travels to each side. |
| `processingCurve` | `number` | `2.1` | How the sweep eases into each turn. |
| `cornerFollow` | `number` | `0.45` | How much the glow rides the corner arcs while processing. |
| `colorVariant` | `'colorful' \| 'mono' \| 'ocean' \| 'sunset' \| …` | `'colorful'` | Palette for the lobes. |
| `colors` | `string[]` | none | Up to seven lobe colours, centre first. |
| `bandColors` | `{ core?, above?, mid?, below? }` | theme defaults | Colours of the band's ridge and fringes. |
| `theme` | `'dark' \| 'light' \| 'auto'` | `'dark'` | Background adaptation; `auto` follows the system. |
| `staticColors` | `boolean` | `false` | Turns off the slow hue drift. |
| `hueRange` | `number` | `24` / `40` | Hue drift range in degrees (dark / light default). |
| `hueDuration` | `number` | `12` / `8.5` | Hue drift period in seconds (dark / light default). |
| `active` | `boolean` | `true` | Off fades the beam out and stops the audio analysis. |
| `paused` | `boolean` | `false` | Freezes glow, band, and analysis on their last frame. |
| `borderRadius` | `number` | auto-detected | Corner radius in px. |
| `brightness` / `saturation` | `number` | theme defaults | Glow multipliers. |
| `glowSize` | `number` | `1` | Bloom blur radius multiplier. |
| `strokeOpacity` / `innerOpacity` / `bloomOpacity` | `number` | `1` | Opacity multipliers of the stroke, inner light, and bloom layers. |
| `scale` | `number` | `1` | Multiplies every pixel dimension at once. |
| `bend`, `bandStrength`, `bandWidth`, `bandPosition`, `bandCurve`, `bandSpread`, `bandSkew`, `bandOffset`, `bandTail`, `bandTailPosition`, `bandTailCurve`, `bandTailOverflow`, `bandAberration` | `number` | tuned | Shape of the glow's contour and the band along it. |
| `distortion`, `distortionDetail` | `number` | `0.62`, `2.3` | Strength and noise grain of the warp under the band line. |
| `glowWidth`, `glowHeight`, `lobeSpacing`, `rangeWidth`, `rangeHeight`, `softness`, `coreSize`, `coreLight`, `coreLightWidth`, `coreLightHeight`, `strokeScale`, `innerScale`, `innerHeight`, `bloomScale`, `bloomHeight` | `number` | tuned | Lobe, visible-range, core, and halo geometry. |
| `strength` | `number` | `1` | Overall effect opacity (0-1); the children are untouched. |
| `css` | `string` | none | CSS appended after the generated stylesheet; `{id}` is replaced per instance. |

### Slots

| Slot | Props | Description |
|------|------|-------------|
| `default` | - | The wrapped element; the beam layers render behind and above it. |

### Events

| Event | Payload | Description |
|------|------|-------------|
| `level` | `(level: number)` | Fires every frame with the smoothed level the beam shows. |
| `activate` | - | Fires when the fade-in completes. |
| `deactivate` | - | Fires when the fade-out completes. |

### CSS Variables

| Variable | Source | Description |
|------|------|-------------|
| `--voice-strength` | `strength` | Beam-layer opacity (0-1). |
| `--voice-stroke-opacity` / `--voice-inner-opacity` / `--voice-bloom-opacity` | host CSS | Per-layer opacity multipliers, multiplied with `strokeOpacity` / `innerOpacity` / `bloomOpacity`; the component never sets them. |

## Overview

- It clips to the child's `border-top-left-radius` (16 px when none is found), so the beam hugs the element edge.
- One shared `requestAnimationFrame` loop, capped at about 60 fps, drives every instance; an instance scrolled offscreen (256 px margin) unregisters and releases its analyser.
- One `AudioContext` is shared per page, with one source node per stream (reference-counted) and one analyser per instance. Audio is analysed, never played.
- With a `stream`, the analyser reads the RMS level plus three voice bands (80-300, 300-2000, 2000-6000 Hz).
- Under reduced motion, the idle breathing, colour flow, hue drift, distortion, and processing sweep stop; the reaction to sound stays, since it is a meter.

## Technologies

- `styles.ts`, `presets.ts`, `voice-driver.ts`, `audio.ts`, and `color.ts` are verbatim ports of [Jakubantalik/Libraries · voice-glow](https://github.com/Jakubantalik/Libraries/tree/main/packages/voice-glow) (MIT © Jakub Antalik); `useMicrophone` is the Vue port of its React hook.
- Source: `packages/tuffex/packages/components/src/voice-beam/`.

<TuffDocSourceLink />
