JGuang

Voice Glow

Voice Glow · Libraries.dev

Preview
npm install voice-glow@0.3.0

Voice Glow

Install: npm install voice-glow@0.3.0

When to use

Visualize a simulated or permissioned audio level around content.

Key native props

PropMeaning
lookglow, dots or lines. Color palettes do not recolor the monochrome dots/lines surfaces.
levelManual 0–1 number or getter. A getter avoids React updates for every audio frame.
streamA provided MediaStream takes precedence over level. Obtain it only after an explicit user gesture and permission.
activeFade out and stop analysis; paused holds the current frame instead. idle=0 removes resting glow in the silent example.
themePass app light/dark; auto follows system preference. onLevel runs per frame: avoid setState on every call.

Interaction and state checks

Move the simulated level from zero to one, switch looks, toggle active and pause. Test keyboard range input and ensure no microphone prompt appears just by opening this preview.

Integration boundaries

No recording, transcription or voice backend is included. The example never calls useMicrophone/getUserMedia; supplying a real stream is a separate application responsibility.

Example files and host setup

Copy the component file and shared.tsx from examples in the source panel, keeping their relative locations; do not copy only the upstream implementation. Install the pinned npm package and use its own export in a browser component. Examples need no Tailwind or competing theme provider, read host html language/theme and provide reduced-motion/graphics fallbacks. Native implementation references are for reading; import via npm rather than copying an incomplete engine. Showcase labels are not new native props. Upstream detailed prose remains English; no full-translation claim is made.

Runnable usage

"use client";
import { useState } from "react";
import { VoiceBeam, type VoiceBeamLook } from "voice-glow";
import { DemoFrame, GraphicsGuard, control, demoTheme, panel, t } from "./shared";
export default function Example({ variant = "Default" }: { variant?: string }) {
  const [look, setLook] = useState<VoiceBeamLook>(variant === "Dots" ? "dots" : variant === "Lines" ? "lines" : "glow"), [level, setLevel] = useState(variant === "Silent" ? 0 : .45), [active, setActive] = useState(variant !== "Inactive"), [paused, setPaused] = useState(variant === "Paused");
  return <DemoFrame status={`look=${look}, level=${level.toFixed(2)}, active=${active}, paused=${paused}`} controls={<>
    <label>{t("Look", "外观")} <select style={control} aria-label={t("Voice appearance", "声音外观")} value={look} onChange={e => setLook(e.target.value as VoiceBeamLook)}>{["glow", "dots", "lines"].map(s => <option key={s}>{s}</option>)}</select></label>
    <label>{t("Simulated level", "模拟音量")} <input aria-label={t("Simulated voice level", "模拟声音音量")} type="range" min="0" max="1" step="0.01" value={level} onChange={e => setLevel(Number(e.target.value))} /></label>
    <button type="button" style={control} aria-pressed={active} onClick={() => setActive(!active)}>{active ? t("Deactivate", "关闭特效") : t("Activate", "开启特效")}</button>
    <button type="button" style={control} aria-pressed={paused} onClick={() => setPaused(!paused)}>{paused ? t("Resume", "继续动画") : t("Pause", "暂停动画")}</button>
  </>}><GraphicsGuard><VoiceBeam look={look} level={level} active={active} paused={paused} idle={variant === "Silent" ? 0 : .23} theme={demoTheme()} style={{ width: "100%", maxWidth: 320 }}><div style={panel}>{t("Simulated audio, no microphone", "模拟声音,不使用麦克风")}</div></VoiceBeam></GraphicsGuard></DemoFrame>;
}

Preview states

Default, Dots, Lines, Silent, Inactive, Paused

Demo state and mock data do not provide a backend service. Inspect the component props before wiring real data.

Voice

voice-glow on npm.

Sound-reactive glow for React. A centered, colorful beam along the bottom edge of any element — a chat input, a search bar, a card — that rises and blooms with the intensity of a voice. Feed it a microphone stream, or drive it yourself.

It is the voice-shaped sibling of border-beam's line type: the same three-layer construction (edge stroke, inner light, blurred bloom), the same eight palettes, but the glow sits still at the center and its height follows the sound instead of traveling.

Install

npm install voice-glow

Quick start

import { VoiceBeam, useMicrophone } from 'voice-glow';

function Composer() {
  const mic = useMicrophone();

  return (
    <>
      <VoiceBeam stream={mic.stream}>
        <div style={{ padding: 32, borderRadius: 20, background: '#1d1d1d' }}>
          Listening…
        </div>
      </VoiceBeam>
      <button onClick={mic.state === 'live' ? mic.stop : mic.start}>
        {mic.state === 'live' ? 'Stop' : 'Listen'}
      </button>
    </>
  );
}

The component wraps your content and overlays the glow. It auto-detects the border-radius of the first child element. Call mic.start() from a click: browsers only grant the microphone — and Safari only starts audio — inside a user gesture.

Types

The glow is authored for a ~350px chat input. type retunes its geometry for other hosts — every geometry prop below still overrides the preset:

<VoiceBeam type="pill">      {/* a ~150×44 recording pill: glow pulled in, shallow bend, thin band */}
  <RecordingPill />
</VoiceBeam>

<VoiceBeam type="mobile">    {/* the bottom of a phone screen: wider range, taller rise, broad band */}
  <VoiceScreen />
</VoiceBeam>

scale sizes the whole effect as one thing — lobes, spacing, range, bend, band, core, blur and flow all multiply by it — on top of the preset and any individual geometry prop:

<VoiceBeam type="pill" scale={1.3}>

The wrapper carries data-voice-type. voiceDefaults, voiceTypePresets and resolveVoiceDefaults(type) are exported so a host can read the numbers a preset resolves to.

Driving it yourself

Without a stream, the beam follows level (0–1). Pass a number when it changes occasionally, or a getter when it changes every frame — the getter is sampled by the driver without re-rendering your tree:

// From your own analyser, a playback meter, a speech API's volume event…
<VoiceBeam level={() => meter.current}>
  <Card />
</VoiceBeam>

// Or a plain value
<VoiceBeam level={speaking ? 0.8 : 0}>
  <Card />
</VoiceBeam>

Set idle={0} if you want nothing at all while silent; by default the beam keeps a soft breathing presence so it never looks dead.

Response

Four knobs shape how the glow answers the sound:

<VoiceBeam
  sensitivity={2}     // input gain — raise for quiet sources
  threshold={0.03}    // noise gate: below this is silence
  attack={0.1}        // seconds to rise
  release={0.5}       // seconds to settle
>

And four shape the glow itself:

<VoiceBeam
  reach={1.4}   // how tall it grows at full level
  spread={0.6}  // how far it widens; the side lobes drift outward too
  flow={80}     // px/s the spectrum travels sideways at full level
  bands         // low / mid / high bands move the lobes independently (default)
>

flow is what makes the colours move: while a voice is heard the lobes slide left to right (right to left for a negative value), wrapping around at the edges, so every colour takes a turn at the centre; as the voice stops the flow settles with it. flow={0} holds the spectrum still.

With bands on, the center lobe follows the lows, its neighbours the mids and the outer pair the highs, so a voice makes the colors ripple outward rather than one blob pumping. When driving with level, the bands are synthesised from it, so the lobes still dance.

Motion

motion moves the whole glow: how far its lobes are gathered into one compact beam, where that beam sits, the level it is held at while gathered. At rest (the default) the glow is the voice glow. Pass a getter for anything that moves — it is sampled every frame without re-rendering your tree:

<VoiceBeam motion={() => ({ gather: 1, offset: 0.4, heldLevel: 0.5 })}>
FieldRangeWhat
gather0–10 the voice glow, 1 the lobes gathered into one beam
offset× half the lobe ringwhere the beam sits (0 = centre)
stretch0–1extra width while it moves
heldLevel0–1the level the glow is held at while gathered, so the beam has colour
cornerFollow0–1how much the beam rides the element's corner arcs

While gathered the distortion settles out in about a quarter second — once it is gone the wrapper carries data-voice-warp="off" and the warp layers leave the paint — and it eases back as the beam releases.

Processing — the beam sweeping the range while something is transcribing or thinking, eased into each turn and morphing in and out — is a Libraries Pro feature built on motion: the Pro skill adds a useVoiceProcessing(thinking) hook to your project.

Shape

bend is the gravity: as the voice rises, the ceiling the glow is masked to gains extra height at the centre — none at the ends — and a faint rim traces that curve, so the glow's top contour humps upward like space bending around the voice. It is in px at full level (default 60; 0 keeps the plain ellipse). rangeWidth sets how wide the hump is.

The band is the light along that contour — an organic bell, exp(-(|x| / spread)^curve), drawn on its own canvas layer and flattening onto the edge at both ends. It fades in with the bend and reacts to the voice on its own — its red and blue fringes split further from the core and it thickens as the level rises, and it turns with the glow's hue drift:

<VoiceBeam
  bandStrength={1.55}   // opacity; 0 hides it
  bandWidth={2.15}      // thickness
  bandPosition={0.35}   // peak height as a fraction of the glow's ceiling
  bandCurve={1.75}      // bell exponent: < 2 exponential / cusp-like, 2 gaussian, > 2 flat-topped
  bandSpread={0.87}     // bell width vs the half-range: small = spike with long tails, large = dome
  bandSkew={0.12}       // asymmetry; positive widens the right, steepens the left
  bandOffset={-27}      // px shift of the whole line; negative sinks it
  bandTail={0.59}       // the ends rise again in the corners (× the peak)
  bandTailPosition={0.67} // the rise starts 67% of the way to the edge, full lift at the corner
  bandTailCurve={2.4}   // higher = a sharper hook
  bandTailOverflow={15} // runs 15px past each side; the component crops the hook
  bandAberration={0.89} // how far the red / blue fringes split, 0–1
>

Distortion warps the glow under the band sideways: a slowly drifting noise field displaces the inner light and the bloom horizontally (an SVG feDisplacementMap), stronger as the voice rises, so the colours shimmer and stretch like light through bent space. Only the glow under the band line warps — the layers are split at the line with a per-frame clip-path — and it is independent of bandStrength, so the line can be invisible and the warp strong. The edge stroke stays crisp. It is off while the glow is gathered (see Motion).

<VoiceBeam
  distortion={0.62}       // 0 turns the filter off
  distortionDetail={2.3}  // < 1 broad slow waves, > 1 finer ripples
>

Twelve multipliers (defaults are the tuned geometry) reshape the resting glow; the voice still drives the motion on top:

<VoiceBeam
  bend={24}          // px the top contour humps up at full level
  glowWidth={1.2}    // every lobe wider
  glowHeight={0.8}   // every lobe shorter
  lobeSpacing={1.3}  // lobes further apart (and a longer flow ring)
  rangeWidth={1.5}   // the visible ellipse wider
  rangeHeight={1}    // … and taller
  softness={1.2}     // softer lobe edges (below 1: crisper)
  coreSize={1.4}     // the white hot spot at the centre
  strokeScale={1}    // colours in the 1px edge stroke
  innerScale={1}     // the soft light inside the element
  innerHeight={1.5}  // … reaching further in
  bloomScale={1}     // the blurred halo
  bloomHeight={1.3}  // … climbing higher
>

Color variants

<VoiceBeam colorVariant="colorful" />  {/* Full spectrum (default) */}
<VoiceBeam colorVariant="mono" />      {/* Grayscale */}
<VoiceBeam colorVariant="ocean" />     {/* Blue-purple tones */}
<VoiceBeam colorVariant="sunset" />    {/* Orange-yellow-red tones */}
<VoiceBeam colorVariant="forest" />    {/* Green-teal tones */}
<VoiceBeam colorVariant="candy" />     {/* Pink-magenta tones */}
<VoiceBeam colorVariant="ice" />       {/* Cyan-pale blue tones */}
<VoiceBeam colorVariant="gold" />      {/* Amber-yellow tones */}

All variants except mono drift slowly through a hue cycle (hueRange, hueDuration); staticColors holds them still.

Your own colours, over any variant:

<VoiceBeam
  colors={['#ffc915', '#7ec4ff', '#b428e6', '#eb64a0', '#ffb07a', '#9aa0ff', '#7fd9ee']}  // 7 lobes: centre, then pairs outward
  bandColors={{ core: '#c58bff', above: '#ff7ab6', mid: '#7ec4ff', below: '#2dffab' }}      // the band's ridge and fringes
>

Slots you leave out keep the variant's colour for the theme. Light mode ships a candy palette (gold, sky, violet, rose, peach) with a violet-cored band; dark keeps the deeper spectrum and a white-cored band.

Theme

<VoiceBeam theme="dark" />   {/* Dark background (default) */}
<VoiceBeam theme="light" />  {/* Light background */}
<VoiceBeam theme="auto" />   {/* Follows prefers-color-scheme */}

Strength and play / pause

<VoiceBeam strength={0.7} active={listening} onDeactivate={() => console.log('faded out')}>
  <Card />
</VoiceBeam>

strength scales the beam layers only, never the children. active={false} fades the beam out and stops the audio analysis.

Props

PropTypeDefaultDescription
childrenReactNode—Content to wrap
type'default' | 'pill' | 'mobile''default'Host preset; sets the geometry defaults
scalenumber1Size of the whole effect — every px dimension at once
streamMediaStream | null—Audio to react to; wins over level
levelnumber | () => number0Manual drive, 0–1
sensitivitynumber3.1Input gain on the analysed audio
thresholdnumber0.015Noise gate, 0–1
attacknumber0.325Seconds to rise toward a louder level
releasenumber0.86Seconds to settle after the sound drops
idlenumber0.23Resting presence while silent, 0–1
breatheDurationnumber5.2Period of the idle breathing, seconds
reachnumber1.2Height gain at full level (1.8 on light; 1.35 for pill, 3 for mobile)
spreadnumber1.05Width gain at full level (0.8 on light; 1.1 for pill, 0.45 for mobile)
bandsbooleantrueLet the frequency bands move the lobes independently
flownumber48Sideways travel of the spectrum, px/s at full level; negative reverses, 0 holds
motionVoiceBeamMotion | () => VoiceBeamMotionrestThe glow gathered into a beam and where it sits (see Motion); Processing is Pro
colorVariant'colorful' | 'mono' | 'ocean' | 'sunset' | 'forest' | 'candy' | 'ice' | 'gold''colorful'Color palette
colorsstring[]—Up to 7 lobe colours overriding the palette
bandColors{ core?, above?, mid?, below? }—The band's ridge and fringe colours
theme'dark' | 'light' | 'auto''dark'Background adaptation
staticColorsbooleanfalseDisable the hue drift
hueRangenumber24 / 40Hue drift range in degrees (dark / light)
hueDurationnumber12 / 8.5Hue drift period in seconds (dark / light)
activebooleantrueWhether the effect is on
pausedbooleanfalseFreezes the effect in place (glow, band and analysis hold their last frame) without fading it out
borderRadiusnumberauto-detectedCustom border radius in px
brightnessnumber1.15 / 0.95Glow brightness multiplier (dark / light; the dark theme itself is 1.1, pill runs 1.35, mobile 1.2 on dark)
saturationnumber1.2 / 1.6Glow saturation multiplier (dark / light; pill and mobile run 1.5 on dark)
glowSizenumber1Multiplies the bloom blur radius
strokeOpacity / innerOpacity / bloomOpacitynumber1Per-layer opacity multipliers on the theme's own (same as the --voice-*-opacity CSS hooks)
bendnumber60Px the glow's top contour humps up at the centre at full level; 0 is the plain ellipse
bandStrengthnumber1.55 (1.8 for mobile; on light 1.7, or 2 for pill)Opacity of the band along the contour; 0 hides it
bandWidthnumber2.15Thickness of the band
bandPositionnumber0.35Peak height as a fraction of the glow's ceiling
bandCurvenumber1.75Bell exponent; < 2 exponential, 2 gaussian, > 2 flat-topped
bandSpreadnumber0.87Bell width vs the half-range
bandSkewnumber0.12Asymmetry, −0.6–0.6
bandOffsetnumber-27Px shift of the whole band line
bandTailnumber0.59Rise of the band's ends in the corners, × the peak; 0 for pill
bandTailPositionnumber0.67Where the rise starts, × the centre-to-edge distance; full lift at the corner
bandTailCurvenumber2.4Exponent of the rise; higher = a sharper hook at the corner
bandTailOverflownumber15Px the band runs past each side, so the hook is cropped by the component
bandAberrationnumber0.89Chromatic split of the band's fringes, 0–1
distortion (off on WebKit / Safari for large hosts, see notes)number0.62Horizontal warp of the glow under the band, 0–1; 0 removes the filter
distortionDetailnumber2.3Grain of the distortion noise
glowWidth / glowHeightnumber0.65 / 1.25Width / height of every lobe, all layers
lobeSpacingnumber0.85Distance between lobes, and the flow ring
rangeWidth / rangeHeightnumber0.75 / 1The visible ellipse the glow is masked to
softnessnumber1.07Lobe edge fade: below 1 crisper, above 1 softer
coreSizenumber1The white hot spot at the centre
coreLightnumber0The epicentre: a white wash under the band line so the source reads lighter than the band, 0–3 (1.8 on light; past 1 the solid core widens and the wash grows)
coreLightWidth / coreLightHeightnumber1Size of the epicentre wash
strokeScalenumber1Size of the colours in the edge stroke
innerScale / innerHeightnumber1Size and reach of the inner light
bloomScale / bloomHeightnumber1Size and climb of the blurred halo
strengthnumber1 / 0.8Effect opacity (0–1), beam layers only (dark / light; mobile is 1 on both)
classNamestring—Additional class on the wrapper
styleCSSProperties—Additional inline styles on the wrapper
cssstring—Extra CSS appended after the generated stylesheet; write {id} for the instance id
onLevel(level: number) => void—Called every frame with the smoothed level
onActivate() => void—Called when the fade-in completes
onDeactivate() => void—Called when the fade-out completes

All standard HTMLDivElement attributes are also forwarded to the wrapper. The wrapper carries data-listening while a stream is attached, for styling the host.

CSS hooks

The generated stylesheet reads a few custom properties with a fallback of 1, so a host can retune a layer without touching the props:

VariableLayer
--voice-stroke-opacityThe colored 1px edge stroke
--voice-inner-opacityThe soft light inside the element
--voice-bloom-opacityThe blurred halo
--voice-band-opacityThe band along the bend
--voice-hue-baseA fixed hue shift added to the drift (deg)

The driver also writes --vb-level-{id} (the smoothed 0–1 level) on the wrapper each frame, for anything else in the host that wants to follow the voice.

The wrapper carries data-voice-type, so the host can style its own controls per type.

useMicrophone

const { stream, state, error, supported, start, stop } = useMicrophone({
  constraints: { deviceId: '…' },  // extra getUserMedia audio constraints
  autoStart: false,
});

state is one of idle, requesting, live, denied, unsupported, error. By default the hook turns the browser's echo cancellation, noise suppression and auto gain off, so the beam sees the real dynamics of the voice; pass constraints: {} to keep the browser defaults. The stream is stopped on unmount.

The hook is a convenience; any MediaStream with an audio track works, including a remote WebRTC peer's — a voice call UI can light up with the other side's voice.

How it works

VoiceBeam renders a wrapper <div> with three bottom-centered layers, clipped to the element:

  • ::after — the stroke: the palette painted into the 1px edge ring, with a hot white core at the center
  • ::before — the inner glow: the same lobes as soft light inside the element, faded off at the corners
  • [data-voice-beam-bloom] — the bloom: a blurred halo above the content
  • [data-voice-beam-warp="inner"] / [data-voice-beam-warp="bloom"] — with distortion on, mirrors of the two soft layers clipped to below the band line, carrying the displacement filter
  • [data-voice-beam-band] — a canvas the driver draws the band on, above everything

Every lobe's size and position multiplies a per-instance custom property. A single shared requestAnimationFrame loop (capped at ~60 fps) reads the audio each frame — RMS level plus low / mid / high band energy from one AnalyserNode per instance — shapes it (gain, gate, soft saturation), follows it with an attack/release envelope, advances the flow, folds in the idle breathing and the hue drift, and writes the properties. The browser does the painting; the loop is a few arithmetic ops per instance.

The Web Audio graph is one shared AudioContext, one source node per stream (reference-counted, so several beams can share a microphone), and nothing connected to the output — the audio is analysed, never played. The loop pauses while the instance is inactive or scrolled offscreen, and under prefers-reduced-motion the breathing, flow and hue drift stop while the reaction to sound stays, since that is a meter rather than decoration.

Project structure

voice-glow/
├── src/
│   ├── index.ts           # Public exports
│   ├── VoiceBeam.tsx      # React component
│   ├── types.ts           # TypeScript type definitions
│   ├── styles.ts          # CSS generation engine, palettes and lobe geometry
│   ├── voiceDriver.ts     # Shared rAF loop: analysis, envelope, custom properties
│   ├── audio.ts           # Shared AudioContext and analyser leases
│   └── useMicrophone.ts   # getUserMedia hook
├── dist/                  # Built output (ESM + CJS + types)
├── package.json
├── LICENSE
└── README.md

Requirements

  • React 18+
  • Web Audio API and getUserMedia for the microphone path (every modern browser; a secure context — https or localhost — is required for the microphone)
  • CSS @property for the interpolated fade in / out (Chrome 85+, Safari 15.4+, Firefox 128+); without it the fade steps rather than tweens
  • Phones: the soft layers (inner light, bloom, their warp mirrors, the epicentre) are rastered at half resolution and scaled back up by the compositor — the same picture for blurred gradients at a quarter of the raster and filter work — the band canvas caps its backing store at 2×, and when a device still cannot hold 60 the driver updates every other frame (the glow's dynamics are far slower than 30 Hz) and probes full rate again every few seconds. The halving is explicit (a per-layer factor on every length), so it runs on every engine.
  • WebKit / Safari notes: the distortion warp is off there on hosts larger than about 60,000 px² (a chat input keeps it, a phone screen or its crop does not) (Safari evaluates SVG filters on HTML content on the CPU every paint, which on a phone-sized host costs an order of magnitude in frame rate; a chat input or pill keeps it), and the band's blur runs in CSS on two canvases instead of the 2D context's filter, which Safari lacks. The look otherwise matches Chromium.

Accessibility

The effect layers are purely decorative and use pointer-events: none. They do not affect keyboard navigation or screen readers. Under prefers-reduced-motion: reduce the idle breathing, the flow and the hue drift are disabled. The microphone is only ever requested by your own call to start().

License

MIT

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

VoiceBeam({
      children,
      type = 'default',
      look: lookProp = 'glow',
      dotSize = 1,
      dotGap = 1,
      dotShape = 'round',
      lineWidth = 1,
      lineGap = 1,
      linePattern = 'rows',
      seeThrough = false,
      texture = 0.6,
      gravity = 1,
      surfaceHeight = 1,
      surfaceCurve = 1,
      surfaceTail = 0,
      surfaceTailPosition = 0.6,
      surfaceTailCurve = 2.4,
      surfaceFade = 0.2,
      scale: scaleProp,
      stream = null,
      level = 0,
      sensitivity = 3.1,
      threshold = 0.015,
      attack = 0.325,
      release = 0.86,
      idle: idleProp,
      breatheDuration = 5.2,
      reach: reachProp,
      spread: spreadProp,
      bands = true,
      flow: flowProp,
      motion = null,
      colorVariant = 'colorful',
      colors,
      bandColors,
      theme = 'dark',
      staticColors = false,
      hueRange: hueRangeProp,
      hueDuration: hueDurationProp,
      active = true,
      paused = false,
      borderRadius: customBorderRadius,
      brightness: brightnessProp,
      saturation: saturationProp,
      glowSize: glowSizeProp,
      strokeOpacity: strokeOpacityProp,
      innerOpacity: innerOpacityProp,
      bloomOpacity: bloomOpacityProp,
      bend: bendProp,
      bandStrength: bandStrengthProp,
      bandWidth: bandWidthProp,
      bandPosition: bandPositionProp,
      bandCurve: bandCurveProp,
      bandSpread: bandSpreadProp,
      bandSkew: bandSkewProp,
      bandOffset: bandOffsetProp,
      bandTail: bandTailProp,
      bandTailPosition: bandTailPositionProp,
      bandTailCurve: bandTailCurveProp,
      bandTailOverflow: bandTailOverflowProp,
      bandAberration: bandAberrationProp,
      distortion: distortionProp,
      distortionDetail: distortionDetailProp,
      glowWidth: glowWidthProp,
      glowHeight: glowHeightProp,
      lobeSpacing: lobeSpacingProp,
      rangeWidth: rangeWidthProp,
      rangeHeight: rangeHeightProp,
      softness: softnessProp,
      coreSize: coreSizeProp,
      coreLight: coreLightProp,
      coreLightWidth: coreLightWidthProp,
      coreLightHeight: coreLightHeightProp,
      strokeScale: strokeScaleProp,
      innerScale: innerScaleProp,
      innerHeight: innerHeightProp,
      bloomScale: bloomScaleProp,
      bloomHeight: bloomHeightProp,
      strength: strengthProp,
      className,
      style,
      css: extraCss,
      onLevel,
      onActivate,
      onDeactivate,
      onAnimationEnd: consumerOnAnimationEnd,
      ...props
    }: VoiceBeamProps, ref: ForwardedRef<HTMLDivElement>)
export type VoiceBeamType = 'default' | 'pill' | 'mobile';

export type VoiceBeamTheme = 'dark' | 'light' | 'auto';

export type VoiceBeamLook = 'glow' | 'dots' | 'lines';

export type VoiceBeamDotShape = 'round' | 'square';

export type VoiceBeamLinePattern = 'rows' | 'columns' | 'grid';

export type VoiceBeamColorVariant =
  | 'colorful'
  | 'mono'
  | 'ocean'
  | 'sunset'
  | 'forest'
  | 'candy'
  | 'ice'
  | 'gold';

export type VoiceBeamLevel = number | (() => number);

export interface VoiceBeamMotion {
  gather: number;
  offset?: number;
  stretch?: number;
  heldLevel?: number;
  cornerFollow?: number;
}

export interface VoiceBeamProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
  /** Content to wrap with the voice beam effect */
  children: ReactNode;

  /**
   * Host preset: 'default' (chat input), 'pill' (recording pill) or
   * 'mobile' (bottom of a phone screen). Sets the defaults of the
   * geometry props below for that host; any prop you pass still wins.
   * @default 'default'
   */
  type?: VoiceBeamType;

  /**
   * How the voice is drawn: coloured light, or a surface of dots or lines
   * that the voice raises and gravity brings down.
   * @default 'glow'
   */
  look?: VoiceBeamLook;

  /**
   * Dot radius, as a multiplier on the tuned size. `look="dots"` only.
   * @default 1
   */
  dotSize?: number;

  /**
   * Spacing between dots, as a multiplier — below 1 is a denser, finer
   * surface, above 1 a sparser, coarser one. `look="dots"` only.
   * @default 1
   */
  dotGap?: number;

  /**
   * The dots' shape: round, or square for a pixel grid. `look="dots"` only.
   * @default 'round'
   */
  dotShape?: VoiceBeamDotShape;

  /**
   * Line width, as a multiplier on the tuned width (lines thin out into the
   * distance). `look="lines"` only.
   * @default 1
   */
  lineWidth?: number;

  /**
   * Spacing between lines, as a multiplier — the rows and the columns alike.
   * `look="lines"` only.
   * @default 1
   */
  lineGap?: number;

  /**
   * Which way the lines run: across the surface, into the depth, or both.
   * `look="lines"` only.
   * @default 'rows'
   */
  linePattern?: VoiceBeamLinePattern;

  /**
   * Draw the lines as a wireframe: those behind a raised ridge show through
   * it instead of being hidden by it. `look="lines"` only.
   * @default false
   */
  seeThrough?: boolean;

  /**
   * Organic texture, 0–1: a slow ripple the flow carries across the
   * surface. 0 is a still sheet that only the voice moves. Dots and lines.
   * @default 0.6
   */
  texture?: number;

  /**
   * How hard the surface falls when the voice drops, as a multiplier on its
   * gravity. Lower floats down, higher drops like sand. Dots and lines.
   * @default 1
   */
  gravity?: number;

  /**
   * How tall the surface stands along the bottom, as a multiplier — how far
   * back the sheet runs, and so how much of the host it covers. Dots and lines.
   * @default 1
   */
  surfaceHeight?: number;

  /**
   * How far the surface arcs, as a multiplier: its sides and far edge fall
   * away like a horizon. 0 is a flat sheet; below 0 it cups upward instead.
   * Dots and lines.
   * @default 1
   */
  surfaceCurve?: number;

  /**
   * The surface's tails: how far its ends rise toward the corners, as a share
   * of the sheet's height — the ends curl up into the host's corners instead
   * of running flat into its sides. Below 0 they drop away instead. Dots and lines.
   * @default 0
   */
  surfaceTail?: number;

  /**
   * Where the tails start, as a share of the distance from the centre to
   * the side (0.8 = only the last 20%); they reach their full lift at the edge.
   * @default 0.6
   */
  surfaceTailPosition?: number;

  /**
   * Exponent of the tails' rise: 1 a straight ramp, 2 a parabola, higher a
   * hook that stays low and whips up at the edge.
   * @default 2.4
   */
  surfaceTailCurve?: number;

  /**
   * How far in from the sides the surface dissolves, as a share of its
   * half-width, 0–1. 0 runs it hard into the edges; 1 fades it from the
   * centre out. Dots and lines.
   * @default 0.2
   */
  surfaceFade?: number;

  /**
   * Size of the whole effect. Multiplies every pixel dimension at once —
   * lobes, their spacing, the visible range, the bend, the band, the core,
   * the bloom blur, the flow speed — so the glow grows or shrinks as one
   * thing, on top of the type preset and the individual geometry props.
   * @default 1 (0.45 for `type="pill"`)
   */
  scale?: number;

  /**
   * Audio to react to. When set, the beam analyses this stream (level plus
   * low / mid / high bands) and ignores `level`. Get one from the
   * `useMicrophone` hook or any `getUserMedia` / WebRTC source.
   */
  stream?: MediaStream | null;

  /**
   * Manual drive, 0–1, used when no `stream` is given. A number re-renders
   * on change; a getter is sampled once per frame without re-rendering.
   * @default 0
   */
  level?: VoiceBeamLevel;

  /**
   * Input gain on the analysed audio. The default suits a laptop microphone
   * at conversational distance; raise it for quiet sources, lower it for a
   * close or hot one.
   * @default 3.1
   */
  sensitivity?: number;

  /**
   * Noise gate, 0–1. Levels below it read as silence, so room hum and
   * fan noise do not keep the beam twitching.
   * @default 0.015
   */
  threshold?: number;

  /**
   * Seconds the glow takes to rise toward a louder level.
   * @default 0.325
   */
  attack?: number;

  /**
   * Seconds the glow takes to settle after the sound drops.
   * @default 0.86
   */
  release?: number;

  /**
   * Resting presence, 0–1 — how much the beam breathes while silent so it
   * never looks dead. 0 hides it completely between sounds.
   * @default 0.23
   */
  idle?: number;

  /**
   * Period of the idle breathing in seconds.
   * @default 5.2
   */
  breatheDuration?: number;

  /**
   * How tall the glow grows at full level, as a multiple of its resting
   * height. The rise is centered on the bottom edge.
   * @default 1.2 (1.8 on the light theme; 1.35 for `pill`, 3 for `mobile`)
   */
  reach?: number;

  /**
   * How far the glow widens at full level, as a fraction of its resting
   * width. Side lobes drift outward as well, so a loud sound spreads.
   * @default 1.05 (0.8 on the light theme; 1.1 for `pill`, 0.45 for `mobile`)
   */
  spread?: number;

  /**
   * Let the low / mid / high bands move the colored lobes independently
   * (center follows the lows, the sides the mids and highs). With a
   * manual `level` the bands are synthesised from the level, so the lobes
   * still dance. Off, every lobe follows the one level.
   * @default true
   */
  bands?: boolean;

  /**
   * How fast the spectrum travels sideways, in px per second at full
   * level — the lobes slide left to right while a voice is heard and rest
   * when it stops, so every colour takes a turn at the centre. Negative
   * flows right to left; 0 holds the lobes in place.
   * @default 48
   */
  flow?: number;

  /**
   * The glow's large-scale motion, sampled every frame: how far the lobes
   * are gathered into one compact beam, where it sits, the level it is held
   * at. At rest (the default) the glow is the voice glow. Pass a getter for
   * anything that moves; the Pro processing state is one.
   */
  motion?: VoiceBeamMotion | (() => VoiceBeamMotion | null | undefined) | null;

  /**
   * Color variant for the beam
   * @default 'colorful'
   */
  colorVariant?: VoiceBeamColorVariant;

  /**
   * Your own lobe colours, centre first then the pairs outward — up to
   * seven, any CSS hex or rgb. Slots you leave out (or that fail to
   * parse) keep the variant's colour for the theme.
   */
  colors?: string[];

  /**
   * The band's colours: the core ridge and its chromatic fringes above,
   * between and below. Any CSS hex or rgb; unset ones keep the theme's
   * defaults (white core with red / green / blue fringes on dark, a pink
   * core with rose / lavender / sky on light).
   */
  bandColors?: { core?: string; above?: string; mid?: string; below?: string };

  /**
   * Theme mode — adapts beam colors for dark or light backgrounds;
   * 'auto' follows prefers-color-scheme
   * @default 'dark'
   */
  theme?: VoiceBeamTheme;

  /**
   * Disable the slow hue drift for static colors
   * @default false
   */
  staticColors?: boolean;

  /**
   * Hue drift range in degrees (the palette wanders ± this far)
   * @default 24 (dark) / 40 (light)
   */
  hueRange?: number;

  /**
   * Period of the hue drift in seconds
   * @default 12 (dark) / 8.5 (light)
   */
  hueDuration?: number;

  /**
   * Whether the effect is on. Off fades the beam out and stops the audio
   * analysis.
   * @default true
   */
  active?: boolean;

  /**
   * Freezes the effect where it is — the glow, the band and the audio
   * analysis all hold their last frame — without fading it out. Other
   * props still shape the held frame, and the clocks resume where they
   * stopped.
   * @default false
   */
  paused?: boolean;

  /**
   * Custom border radius in pixels. When omitted, the component
   * auto-detects the border-radius of the first child element.
   */
  borderRadius?: number;

  /**
   * Brightness multiplier for the glow
   * @default 1.15 (dark chat input) / 0.95 (light); the dark theme itself is 1.1, `pill` runs 1.35, `mobile` 1.2 on dark
   */
  brightness?: number;

  /**
   * Saturation multiplier for the glow
   * @default 1.2 (dark) / 1.6 (light); `pill` and `mobile` run 1.5 on dark
   */
  saturation?: number;

  /**
   * Multiplies the bloom blur radius, so the halo reads tighter (< 1) or
   * wider and softer (> 1).
   * @default 1
   */
  glowSize?: number;

  /**
   * Per-layer opacity multipliers on the theme's own: the crisp edge
   * stroke, the soft inner light and the blurred bloom. The same knobs
   * as the `--voice-stroke-opacity` / `--voice-inner-opacity` /
   * `--voice-bloom-opacity` CSS hooks.
   * @default 1
   */
  strokeOpacity?: number;
  innerOpacity?: number;
  bloomOpacity?: number;

  /* ── Shape ──────────────────────────────────────────────────────────
   * Each multiplier below defaults to the tuned geometry. They scale
   * the resting sizes; the voice still drives the motion on top. */

  /**
   * Bend — how much, in px, the glow's top contour humps up at the centre
   * at full level. The ceiling the glow is masked to gains that height in
   * the middle and none at the ends, and a faint rim traces the curve, so
   * the glow's outline bows upward with the voice like space bending
   * around it. 0 keeps the contour at its plain ellipse.
   * @default 60
   */
  bend?: number;
  /* ── Band ──────────────────────────────────────────────────────────
   * The light along the bend's contour — an organic bell drawn on its own
   * layer, exp(-(|x| / spread)^curve), flattening onto the edge at both
   * ends. It fades in with the bend, and reacts to the voice on its own:
   * the fringes split further and the band thickens as the level rises,
   * and it turns with the hue drift. */

  /** Opacity of the band, 0 hides it. @default 1.55 (1.8 for `mobile`; on light 1.7, or 2 for `pill`) */
  bandStrength?: number;
  /** Thickness of the band. @default 2.15 */
  bandWidth?: number;
  /** Height of the band's peak as a fraction of the glow's ceiling, 0.1–1.3. @default 0.35 */
  bandPosition?: number;
  /** Bell exponent: below 2 an exponential, cusp-like rise; 2 a gaussian; above, a flatter top. @default 1.75 */
  bandCurve?: number;
  /** Bell width as a fraction of the glow's half-range: small is a narrow spike with long tails, large a broad dome. @default 0.87 */
  bandSpread?: number;
  /** Asymmetry, −0.6–0.6: positive widens the right side and steepens the left. @default 0.12 */
  bandSkew?: number;
  /** Vertical shift of the whole band line in px; negative sinks it toward the edge. @default -27 */
  bandOffset?: number;
  /** How far the band's ends rise again toward the corners, as a fraction of its peak, 0–1 — the line opens upward at the edges instead of dying flat. @default 0.59 (0 for `pill`) */
  bandTail?: number;
  /** Where the tail's rise starts, as a share of the distance from the beam's centre to the element's edge (0.85 = only the last 15%); it always reaches the full lift exactly at the corner. @default 0.67 */
  bandTailPosition?: number;
  /** Exponent of the rise: 1 a straight ramp, 2 a parabola, higher a hook that stays low and whips up at the corner. @default 2.4 */
  bandTailCurve?: number;
  /** Px the band runs past each side of the element, so the hook peaks outside and is cropped by the component's edge. Scales with `scale`. @default 15 */
  bandTailOverflow?: number;
  /** Chromatic aberration — how far the red and blue fringes split from the core, 0–1. @default 0.89 */
  bandAberration?: number;

  /**
   * Distortion — how much the glow under the band warps sideways, 0–1.
   * A slowly drifting noise field displaces the inner light and the bloom
   * horizontally, stronger as the voice rises, so the colours shimmer and
   * stretch like light through bent space. Only the glow UNDER the band
   * line warps — the layers are split at the line — and it is independent
   * of `bandStrength`, so the line can be invisible and the warp strong.
   * The edge stroke stays crisp. 0 turns the filter off entirely.
   * @default 0.62
   */
  distortion?: number;
  /** Grain of the distortion's noise: below 1 broad slow waves, above 1 finer ripples. @default 2.3 */
  distortionDetail?: number;

  /** Width of every colour lobe, all layers — on the dots and lines, the width of each hill. @default 0.65 */
  glowWidth?: number;
  /** Height of every colour lobe, all layers. @default 1.25 */
  glowHeight?: number;
  /** Distance between the lobes (and the ring the flow travels) — on the dots and lines, between the hills. @default 0.85 */
  lobeSpacing?: number;
  /** Width of the visible ellipse the glow is masked to. @default 0.75 */
  rangeWidth?: number;
  /** Height of the visible ellipse the glow is masked to. @default 1 */
  rangeHeight?: number;
  /** Where each lobe fades out — below 1 crisper, above 1 softer. @default 1.07 */
  softness?: number;
  /** The white hot spot at the centre of the edge. @default 1 */
  coreSize?: number;
  /**
   * The epicentre: a soft white wash under the band line at the centre of
   * the edge, so the source reads lighter than the band. 0–3: up to 1 the
   * wash's opacity, past 1 its solid white core widens and the whole wash
   * grows, washing out more of the colour beneath.
   * @default 0 (1.8 on the light theme)
   */
  coreLight?: number;
  /** Width of the epicentre wash. @default 1 */
  coreLightWidth?: number;
  /** Height of the epicentre wash. @default 1 */
  coreLightHeight?: number;
  /** Size of the colours painted into the 1px edge stroke. @default 1 */
  strokeScale?: number;
  /** Size of the soft light inside the element. @default 1 */
  innerScale?: number;
  /** Extra height of the inner light — how far it reaches into the element. @default 1 */
  innerHeight?: number;
  /** Size of the blurred halo. @default 1 */
  bloomScale?: number;
  /** Extra height of the halo — how far the soft light climbs. @default 1 */
  bloomHeight?: number;

  /**
   * Overall strength/opacity of the effect (0–1).
   * Only affects the beam layers — not the children.
   * @default 1 (dark) / 0.8 (light); 1 on both for `type="mobile"`
   */
  strength?: number;

  /**
   * Additional class name for the container
   */
  className?: string;

  /**
   * Additional inline styles for the container
   */
  style?: CSSProperties;

  /**
   * Extra CSS appended after the beam's own generated stylesheet. Write
   * `{id}` wherever the instance id belongs — the root is
   * `[data-voice-beam="{id}"]`, its layers the `::before` / `::after`
   * pseudo-elements and `[data-voice-beam-bloom]`, and keyframes are named
   * `*-{id}` — and it is substituted per instance.
   */
  css?: string;

  /**
   * Called every frame with the smoothed level (0–1) the beam is showing.
   * Handy for a meter or a "listening" label; avoid setting React state
   * from it on every call.
   */
  onLevel?: (level: number) => void;

  /**
   * Callback when the fade-in completes
   */
  onActivate?: () => void;

  /**
   * Callback when the fade-out completes
   */
  onDeactivate?: () => void;
}

Destructuring defaults in source

Only defaults written directly in parameter/props destructuring are listed. Conditional expressions, runtime theme and computed values require source inspection; this is not an exhaustive behavior-default list.

// VoiceBeam
type = 'default'

// VoiceBeam
look = 'glow'

// VoiceBeam
dotSize = 1

// VoiceBeam
dotGap = 1

// VoiceBeam
dotShape = 'round'

// VoiceBeam
lineWidth = 1

// VoiceBeam
lineGap = 1

// VoiceBeam
linePattern = 'rows'

// VoiceBeam
seeThrough = false

// VoiceBeam
texture = 0.6

// VoiceBeam
gravity = 1

// VoiceBeam
surfaceHeight = 1

// VoiceBeam
surfaceCurve = 1

// VoiceBeam
surfaceTail = 0

// VoiceBeam
surfaceTailPosition = 0.6

// VoiceBeam
surfaceTailCurve = 2.4

// VoiceBeam
surfaceFade = 0.2

// VoiceBeam
stream = null

// VoiceBeam
level = 0

// VoiceBeam
sensitivity = 3.1

// VoiceBeam
threshold = 0.015

// VoiceBeam
attack = 0.325

// VoiceBeam
release = 0.86

// VoiceBeam
breatheDuration = 5.2

// VoiceBeam
bands = true

// VoiceBeam
motion = null

// VoiceBeam
colorVariant = 'colorful'

// VoiceBeam
theme = 'dark'

// VoiceBeam
staticColors = false

// VoiceBeam
active = true

// VoiceBeam
paused = false

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

HTMLDivElement
Omit<HTMLAttributes<HTMLDivElement>, 'children'>
HTMLAttributes<HTMLDivElement>
ReactNode
MediaStream
CSSProperties
ForwardedRef<HTMLDivElement>