# Voice visualizations

When users talk to your agent instead of typing, `VoiceGlow` and `VoiceWave` give the agent a visual presence. `VoiceGlow` is a soft globe of drifting color, and `VoiceWave` is a single waveform line. Both show what the agent is doing through the way they move. Pass the agent's state:

```tsx
import { VoiceGlow } from "stream-chat-react/ai-components";

<VoiceGlow state="listening" style={{ width: "20rem", height: "20rem" }} />;
```

>
> **Info:** `VoiceGlow`, `VoiceWave` and `useAudioLevel` are part of the `stream-chat-react/ai-components` entry point. See the [Quickstart](https://getstream.io/chat/docs/sdk/react/ai-components/) to install them and load their stylesheet.
>

The [Voice agent](https://getstream.io/chat/docs/sdk/react/ai-components/use-cases/voice-agent/) use case builds a whole voice screen around them: a call with your agent, the globe driven by the agent's state and voice, and what was said written out as a conversation. The [Voice agent visuals](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/ai/voice-agent-visuals/) cookbook adds them to a chat app.

## States

The state is a `VoiceAgentState`:

| State          | The agent is...         | Motion                                                     |
| -------------- | ----------------------- | ---------------------------------------------------------- |
| `"idle"`       | Waiting to be spoken to | A calm, barely-there shimmer                               |
| `"connecting"` | Not ready yet           | A loader sweep with a pulsing breath                       |
| `"listening"`  | Hearing the user        | Gently expands and contracts                               |
| `"thinking"`   | Working on an answer    | The globe turns slowly, the line carries a travelling wave |
| `"speaking"`   | Talking                 | Strong motion that ebbs and pauses like speech             |

The states change only the motion, never the colors, and a change of state eases from one motion into the next. Set the state straight from your agent's events, without animating it yourself.

Neither component has a size of its own: each draws in the middle of the box you give it, on a transparent background. The globe is about half as wide as the box's shorter side, and the line about 80% as wide, so give the glow a square and the wave a square or a wide box. Without a size of yours, the stylesheet makes the glow a `12rem` square, and the wave `100%` wide and `8rem` tall:

```css
.voice-panel .str-chat__ai-voice-wave {
  height: 6rem;
}
```

## Props

Both components take the same props:

| Prop           | Description                                                                                                                                                                                             | Default           |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `state`        | What the agent is doing.                                                                                                                                                                                | -                 |
| `colors`       | One to five CSS colors to paint it with. Colors past the fifth are ignored. See [Colors](#colors).                                                                                                      | The theme's color |
| `expressivity` | How lively the motion is, from `0` (nearly still) through `1` to `2` (twice as animated). It never changes the colors.                                                                                  | `1`               |
| `audioLevel`   | A function that returns the live level of the voice being heard, from `0` to `1`. Returning `null`, or leaving it out, animates speech on its own. See [Follow the real voice](#follow-the-real-voice). | -                 |

`VoiceGlow` also takes `rendering`. See [Draw the glow with CSS](#draw-the-glow-with-css).

Any other prop of a `div`, such as `className`, `style` or an event handler, goes to the root element.

## Follow the real voice

To drive the motion from the agent's voice, pass `audioLevel` a function that returns the live level. The component reads it once a frame, so a level that changes many times a second re-renders nothing. Keep it up to date at least 15 times a second to follow the words.

`useAudioLevel` makes that function from a `MediaStream`, such as the user's microphone or the agent's audio in a call:

```ts
useAudioLevel(stream: MediaStream | null | undefined): () => number | null
```

The function it returns is stable, and measures the level each time it's called. It returns `null` while there is no stream or the browser has no Web Audio, so the component animates speech on its own. The hook releases its audio graph when the stream changes and on unmount, and leaves the stream's tracks running, since they're yours.

With [Stream Video](https://getstream.io/video/docs/react/), that's the `audioStream` of the agent as a participant of the call. [Participant state](https://getstream.io/video/docs/react/guides/call-and-participant-state/#participant-state) explains how to read the participants:

```tsx
import type { StreamVideoParticipant } from "@stream-io/video-react-sdk";
import {
  useAudioLevel,
  VoiceWave,
  type VoiceAgentState,
} from "stream-chat-react/ai-components";

export const AgentWave = ({
  agent,
  state,
}: {
  agent?: StreamVideoParticipant;
  state: VoiceAgentState;
}) => {
  const agentLevel = useAudioLevel(agent?.audioStream);

  return (
    <VoiceWave
      audioLevel={agentLevel}
      colors={["#7c3aed", "#ec4899", "#14b8a6"]}
      expressivity={1.4}
      state={state}
      style={{ height: "6rem" }}
    />
  );
};
```

To show the user's voice while the agent listens, pass the microphone's stream instead, such as the one from `navigator.mediaDevices.getUserMedia({ audio: true })`. Stop its tracks yourself when you're done with it.

## Colors

Without `colors`, the components use `--str-chat__ai-voice-color`, which follows the SDK's accent color. To use the same palette everywhere, set it once in CSS instead of passing `colors` to each component:

```css
.voice-panel {
  --str-chat__ai-voice-color: #7c3aed;
  --str-chat__ai-voice-backdrop: #0b1020;
}
```

The components also pick a light or a dark look from `--str-chat__ai-voice-backdrop`, which defaults to the SDK's page background. Set it when they sit on a surface whose color differs from the page's. They read both about once a second, and when the system switches between light and dark, so a theme switch needs no prop. [Theming](https://getstream.io/chat/docs/sdk/react/ai-components/#theming) lists the other variables.

## Pointer, accessibility and rendering

- Clicking the globe or the line bounces it, and a hovering mouse or pen ripples it. Only the shape takes the pointer: around it, clicks and hovers reach whatever is behind the box. Pointer events are never stopped, so your own `onClick` on the root or a parent still fires.
- With `prefers-reduced-motion: reduce`, the motion slows to a crawl, and clicks and hovers are ignored.
- The root has `role="img"` and an accessible name made of "Voice agent" and the state, such as "Voice agent: Speaking". The texts go through the SDK's translations, under `aiComponents.voiceVisualization.voiceAgent.ariaLabel` and the state words such as `aiComponents.voiceVisualization.speaking.text`, and an `aria-label` of yours replaces the name. The drawing itself is hidden from assistive technology.
- The component isn't a live region, so screen readers don't announce a new state. If users need to hear it, add a `role="status"` text next to it.
- The components draw on a canvas: the line in 2D, and the globe with a WebGL shader. All the globes on a page share one WebGL context, so the shader compiles once. They draw only while they're on screen and the tab is visible, and fade in once their first frame is ready, so they never flash empty.
- With server rendering, the markup renders on the server, and drawing starts after hydration.

### Draw the glow with CSS

`VoiceGlow`'s `rendering` sets how the globe is drawn. Either way it moves the same; only the look and the cost differ:

- **`"auto"`**, the default, draws it with the WebGL shader, the richest look, with color masses marbled with noise and ripples from the pointer. It falls back to `"css"` when the browser can't create a WebGL context, or loses it.
- **`"css"`** composes it from CSS gradients animated only through `transform` and `opacity`, so the browser's compositor runs the motion. It keeps moving while the page is busy, such as while a long answer streams in, and runs no script per frame unless you pass `audioLevel`. It lacks the shader's fine marbling: a click sends out a ring instead of a ripple, and hovering does nothing. It needs `@property`, container query units and `color-mix()` (Chrome 111, Safari 16.4 and Firefox 128 or later).

```tsx
<VoiceGlow
  aria-label="Assistant is thinking"
  rendering="css"
  state="thinking"
  style={{ width: "8rem", height: "8rem" }}
/>
```

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/ai-components/voice-visualizations/](https://getstream.io/chat/docs/sdk/react/ai-components/voice-visualizations/).