# Persona
An animated AI visual component powered by Rive that responds to different states like listening, thinking, and speaking.
The `Persona` component displays an animated AI visual that responds to different conversational states. Built with Rive WebGL2, it provides smooth, high-performance animations for various AI interaction states including idle, listening, thinking, speaking, and asleep. The component supports multiple visual variants to match different design aesthetics.
See `scripts/persona-obsidian.tsx` for this example.
## Installation
```bash
npx ai-elements@latest add persona
```
## Features
- Smooth state-based animations powered by Rive
- Multiple visual variants (obsidian, mana, opal, halo, glint, command)
- Responsive to five distinct states: idle, listening, thinking, speaking, and asleep
- WebGL2-accelerated rendering for optimal performance
- Customizable size and styling
- Lifecycle callbacks for load, ready, pause, play, and stop events
- TypeScript support with full type definitions
## Variants
The Persona component comes with 6 distinct visual variants, each with its own unique aesthetic:
### Obsidian (Default)
See `scripts/persona-obsidian.tsx` for this example.
### Mana
See `scripts/persona-mana.tsx` for this example.
### Opal
See `scripts/persona-opal.tsx` for this example.
### Halo
See `scripts/persona-halo.tsx` for this example.
### Glint
See `scripts/persona-glint.tsx` for this example.
### Command
See `scripts/persona-command.tsx` for this example.
## Props
### ``
The root component that renders the animated AI visual.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `state` | `unknown` | - | The current state of the AI persona. Controls which animation is displayed. |
| `variant` | `unknown` | - | The visual style variant to display. |
| `className` | `string` | - | Additional CSS classes to apply to the component. |
| `onLoad` | `RiveParameters[` | - | Callback fired when the Rive file starts loading. |
| `onLoadError` | `RiveParameters[` | - | Callback fired if the Rive file fails to load. |
| `onReady` | `() => void` | - | Callback fired when the Rive animation is ready to play. |
| `onPause` | `RiveParameters[` | - | Callback fired when the animation is paused. |
| `onPlay` | `RiveParameters[` | - | Callback fired when the animation starts playing. |
| `onStop` | `RiveParameters[` | - | Callback fired when the animation is stopped. |
## States
The Persona component responds to five distinct states, each triggering different animations:
- **idle**: The default resting state when the AI is not active
- **listening**: Displayed when the AI is actively listening to user input (e.g., during voice recording)
- **thinking**: Shown when the AI is processing or generating a response
- **speaking**: Active when the AI is delivering a response (e.g., text-to-speech output)
- **asleep**: A dormant state for when the AI is inactive or in low-power mode
## React Strict Mode (Vite)
The Persona component uses WebGL2 for rendering. Browsers limit the number of active WebGL2 contexts (~8–16), and React Strict Mode (enabled by default in Vite dev) double-mounts components, which can exhaust that limit and crash the page.
The component includes a built-in guard that defers WebGL2 initialization by one frame, preventing context creation during Strict Mode's throw-away mount. This means the component works in Vite dev mode out of the box — no configuration needed.
If you still experience crashes (for example, when rendering many Persona instances simultaneously), reduce the number of concurrent Persona components on screen.
## Usage Examples
### Basic Usage
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return ;
}
```
### With State Management
```tsx
import { Persona } from "@repo/elements/persona";
import { useState } from "react";
export default function App() {
const [state, setState] = useState<
"idle" | "listening" | "thinking" | "speaking" | "asleep"
>("idle");
const startListening = () => setState("listening");
const startThinking = () => setState("thinking");
const startSpeaking = () => setState("speaking");
const reset = () => setState("idle");
return (
);
}
```
### With Custom Styling
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return (
);
}
```
### With Lifecycle Callbacks
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return (
console.log("Animation ready")}
onLoad={() => console.log("Starting to load")}
onLoadError={(error) => console.error("Failed to load:", error)}
onPlay={() => console.log("Animation playing")}
onPause={() => console.log("Animation paused")}
onStop={() => console.log("Animation stopped")}
/>
);
}
```