orb-ui
v0.6.1
Published
React voice agent UI components with audio-reactive adapters for Vapi, ElevenLabs, LiveKit, Pipecat, OpenAI Realtime, Gemini Live, and custom voice AI apps.
Maintainers
Readme
orb-ui
React voice agent UI components for Vapi, ElevenLabs, LiveKit, Pipecat, OpenAI Realtime, Gemini Live, and custom realtime voice AI apps. orb-ui gives you animated voice orbs, audio-reactive themes, accessible clickable controls, and provider adapters for building polished voice agent interfaces in React.
import Vapi from '@vapi-ai/web'
import { Orb } from 'orb-ui'
import { createVapiAdapter } from 'orb-ui/adapters'
const vapi = new Vapi('your-public-key')
const adapter = createVapiAdapter(vapi, { assistantId: 'your-assistant-id' })
export function VoiceOrb() {
return <Orb adapter={adapter} theme="circle" aria-label="Start voice assistant" />
}Install
Install the component package:
npm install orb-uiProvider adapters are lightweight wrappers around provider SDKs. Install the SDK for the provider you use:
# Vapi
npm install orb-ui @vapi-ai/web
# ElevenLabs Conversational AI
npm install orb-ui @elevenlabs/client
# LiveKit Agents
npm install orb-ui livekit-client
# Pipecat (choose the transport used by your agent)
npm install orb-ui @pipecat-ai/client-js @pipecat-ai/small-webrtc-transport
# OpenAI Realtime uses browser WebRTC and has no additional client SDK
npm install orb-ui
# Gemini Live
npm install orb-ui @google/genaiNote: Orb uses React hooks internally — in Next.js App Router, use it in a
'use client'component.
How provider adapters are created
Every provider ends at the same React API:
<Orb adapter={adapter} theme="circle" aria-label="Start voice assistant" />The only difference is how the adapter obtains a provider session:
| Provider | Required browser setup |
| --------------- | ------------------------------------------------------------------------ |
| Vapi | Pass a configured Vapi client plus assistantId |
| ElevenLabs | Pass Conversation plus an agentId, signed URL, or conversation token |
| LiveKit | Provide a token endpoint and optional agent name |
| Pipecat | Pass a configured PipecatClient plus its connect callback |
| OpenAI Realtime | Return a fresh short-lived client secret from getClientSecret |
| Gemini Live | Open the official Google Live session in connect |
The adapter owns provider event mapping and emits one consistent OrbSignal. OpenAI and Gemini
standard API keys, and LiveKit participant-token signing, stay on your server. See the
adapter overview for the responsibility boundary and
advanced setup shapes.
Quick Start
Use orb-ui as a React voice AI component when you need a first-party provider voice UI or a custom animated voice orb for another realtime voice agent stack.
With Vapi
import Vapi from '@vapi-ai/web'
import { Orb } from 'orb-ui'
import { createVapiAdapter } from 'orb-ui/adapters'
const vapi = new Vapi('your-public-key')
const adapter = createVapiAdapter(vapi, { assistantId: 'your-assistant-id' })
function App() {
return <Orb adapter={adapter} theme="circle" aria-label="Start Vapi assistant" />
}With ElevenLabs
import { Conversation } from '@elevenlabs/client'
import { Orb } from 'orb-ui'
import { createElevenLabsAdapter } from 'orb-ui/adapters'
const adapter = createElevenLabsAdapter(Conversation, { agentId: 'your-agent-id' })
function App() {
return <Orb adapter={adapter} theme="circle" aria-label="Start ElevenLabs assistant" />
}With LiveKit
import { Orb } from 'orb-ui'
import { createLiveKitAdapter } from 'orb-ui/adapters/livekit'
const adapter = createLiveKitAdapter({
tokenEndpoint: '/api/livekit-token',
agentName: 'your-agent-name',
})
function App() {
return <Orb adapter={adapter} theme="circle" aria-label="Start LiveKit assistant" />
}The LiveKit entrypoint creates the room and token source, assigns a fresh room name, and meters both
sides of the conversation with speech-oriented analyser and smoothing defaults. Existing-room and
custom-runtime modes remain available from the advanced orb-ui/adapters entrypoint.
With Pipecat
import { PipecatClient } from '@pipecat-ai/client-js'
import { SmallWebRTCTransport } from '@pipecat-ai/small-webrtc-transport'
import { Orb } from 'orb-ui'
import { createPipecatAdapter } from 'orb-ui/adapters'
const client = new PipecatClient({ transport: new SmallWebRTCTransport(), enableMic: true })
const adapter = createPipecatAdapter(client, {
connect: () => client.connect({ webrtcUrl: 'https://agent.example.com/api/offer' }),
})
function App() {
return <Orb adapter={adapter} theme="circle" aria-label="Start Pipecat assistant" />
}The Pipecat adapter consumes the standard RTVI event surface and meters the client media tracks as a browser fallback, so it works with Pipecat Cloud, Daily, SmallWebRTC, and transports that emit sparse audio-level events. See the Pipecat guide.
With OpenAI Realtime
import { Orb } from 'orb-ui'
import { createOpenAIRealtimeAdapter } from 'orb-ui/adapters'
const adapter = createOpenAIRealtimeAdapter({
getClientSecret: async () => {
const response = await fetch('/api/openai-realtime-token', { method: 'POST' })
return (await response.json()).value
},
})Create client secrets with a standard OpenAI API key on your server. See the OpenAI Realtime guide.
With Gemini Live
import { GoogleGenAI } from '@google/genai'
import { Orb } from 'orb-ui'
import { createGeminiLiveAdapter } from 'orb-ui/adapters'
const adapter = createGeminiLiveAdapter({
connect: async (callbacks) => {
const token = await fetch('/api/gemini-live-token', { method: 'POST' }).then((res) =>
res.json(),
)
const client = new GoogleGenAI({
apiKey: token.value,
httpOptions: { apiVersion: 'v1alpha' },
})
return client.live.connect({ model: token.model, config: token.config, callbacks })
},
})Mint one-use Gemini Live tokens on your server. See the Gemini Live guide for the matching server config that disables automatic activity detection. The adapter handles client-side turn detection by default.
The examples above show the intended happy paths. Transport overrides, custom browser runtimes, existing-session modes, and audio calibration hooks are optional and documented in the individual adapter guides.
Controlled mode (custom integration)
import { Orb } from 'orb-ui'
import { useState } from 'react'
function App() {
const [state, setState] = useState('idle')
const [volume, setVolume] = useState(0)
return <Orb state={state} volume={volume} theme="circle" />
}Use signal when your integration has separate input and output levels:
import { Orb } from 'orb-ui'
function App() {
return <Orb signal={{ state: 'speaking', outputVolume: 0.7 }} theme="circle" />
}External session controls
Keep the orb as a passive visual when start and stop controls belong elsewhere in your layout. Provider adapters already expose the matching lifecycle methods.
function VoiceExperience({ adapter }) {
return (
<>
<Orb adapter={adapter} theme="cloud" interactive={false} />
<button onClick={() => void adapter.start?.()}>Start conversation</button>
<button onClick={() => void adapter.stop?.()}>End conversation</button>
</>
)
}Themes
| Theme | Description |
| -------- | ----------------------------------------------------------------------------- |
| cloud | Atmospheric sphere with inverse listening scale and faster speaking motion. |
| debug | State + volume display with start/stop. Use to verify your integration works. |
| circle | Pulsing circle that reacts to volume. |
| bars | Five bars that animate with voice. |
When an adapter or onStart/onStop handler is provided, visual themes render as keyboard-accessible <button type="button"> controls. Pass interactive={false} to keep the theme passive and call adapter.start() or adapter.stop() from external controls instead.
Props
| Prop | Type | Default | Description |
| ------------- | ------------------------------------------ | --------- | ------------------------------------------------------- |
| theme | 'debug' \| 'circle' \| 'bars' \| 'cloud' | 'debug' | Visual theme |
| signal | OrbSignal | — | Rich controlled signal with state/input/output volume |
| state | OrbState | 'idle' | Conversation state (controlled mode) |
| volume | number | 0 | Audio volume, 0–1. Overrides signal/adapter volume. |
| adapter | OrbAdapter | — | Provider adapter (manages signal updates automatically) |
| size | number | 200 | Size in pixels |
| className | string | — | Optional class name for the rendered theme |
| style | React.CSSProperties | — | Optional inline styles for the rendered theme |
| disabled | boolean | false | Disables clickable themes and debug start/stop controls |
| interactive | boolean | true | Allows the orb itself to start and stop a session |
| aria-label | string | generated | Accessible label for clickable visual themes |
| onStart | () => void | — | Custom start handler (overrides adapter.start()) |
| onStop | () => void | — | Custom stop handler (overrides adapter.stop()) |
States
idle · connecting · listening · thinking · speaking · error
Supported Providers
| Provider | Adapter |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Vapi | createVapiAdapter from orb-ui/adapters |
| ElevenLabs | createElevenLabsAdapter from orb-ui/adapters |
| LiveKit | createLiveKitAdapter from orb-ui/adapters |
| Pipecat | createPipecatAdapter from orb-ui/adapters |
| OpenAI Realtime | createOpenAIRealtimeAdapter from orb-ui/adapters |
| Gemini Live | createGeminiLiveAdapter from orb-ui/adapters |
| Custom | Use controlled mode — pass signal, or state and volume directly |
Development
git clone https://github.com/alexanderqchen/orb-ui.git
cd orb-ui
pnpm install
# Build the library
pnpm build
# Run demo locally
pnpm dev:demoUseful maintenance commands:
pnpm check # format check, lint, typechecks, tests, library build, demo build
pnpm format # format repo files
pnpm changeset # add release notes for a user-facing package changeReleases are managed with Changesets. Merging a Changesets version PR publishes
orb-ui to npm from GitHub Actions using npm trusted publishing.
License
MIT © Alexander Chen
