Skip to content

React Integration

Learn how to integrate Vowel into your React applications using the provided React components and hooks.

Overview

Vowel provides React-specific exports for seamless integration:

  • VowelProvider - Context provider for the Vowel client
  • useVowel - Hook to access the Vowel client
  • useSyncContext - Hook to automatically sync context with the AI
  • VowelAgent - Pre-built voice agent UI component
  • VowelMicrophone - Standalone microphone button component

Installation

bash
npm install @vowel.to/client

Basic Setup

1. Create Vowel Client

First, create and configure your Vowel client:

typescript
// vowel.client.ts
import { Vowel, createDirectAdapters } from '@vowel.to/client';

export function createVowelClient(router: any) {
  const { navigationAdapter, automationAdapter } = createDirectAdapters({
    navigate: (path) => router.push(path),
    routes: [
      { path: '/', description: 'Home page' },
      { path: '/products', description: 'Product catalog' },
      { path: '/cart', description: 'Shopping cart' }
    ],
    enableAutomation: true
  });

  const vowel = new Vowel({
    appId: 'your-app-id',
    navigationAdapter,
    automationAdapter,
    voiceConfig: {
      name: 'Puck',
      language: 'en-US'
    }
  });

  // Register custom actions
  vowel.registerAction('searchProducts', {
    description: 'Search for products',
    parameters: {
      query: { type: 'string', description: 'Search query' }
    }
  }, async ({ query }) => {
    // Your search logic
    return { success: true };
  });

  return vowel;
}

2. Wrap App with VowelProvider

tsx
// App.tsx
import { VowelProvider, VowelAgent } from '@vowel.to/client/react';
import { createVowelClient } from './vowel.client';
import { useRouter } from 'next/navigation';

function App() {
  const router = useRouter();
  const vowel = createVowelClient(router);

  return (
    <VowelProvider client={vowel}>
      <YourApp />
      <VowelAgent position="bottom-right" />
    </VowelProvider>
  );
}

3. Use the Hook

Access the Vowel client anywhere in your component tree:

tsx
import { useVowel } from '@vowel.to/client/react';

function MyComponent() {
  const { client, state } = useVowel();

  const handleNotify = async () => {
    await client.notifyEvent('Hello from React!');
  };

  return (
    <div>
      <p>Connected: {state.isConnected ? 'Yes' : 'No'}</p>
      <p>AI Speaking: {state.isAISpeaking ? 'Yes' : 'No'}</p>
      <button onClick={handleNotify}>Notify</button>
    </div>
  );
}

Components

VowelProvider

Context provider that makes the Vowel client available to all child components.

tsx
import { VowelProvider } from '@vowel.to/client/react';

<VowelProvider client={vowel}>
  {children}
</VowelProvider>

Props:

  • client - The Vowel client instance (required)
  • children - React children

VowelAgent

Pre-built voice agent UI with microphone button and visual feedback.

tsx
import { VowelAgent } from '@vowel.to/client/react';

<VowelAgent 
  position="bottom-right"
  showTranscript={true}
  autoStart={false}
/>

Props:

  • position - 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' (default: 'bottom-right')
  • showTranscript - Show live transcript (default: true)
  • autoStart - Auto-start session on mount (default: false)
  • className - Additional CSS classes
  • style - Inline styles

VowelMicrophone

Standalone microphone button component.

tsx
import { VowelMicrophone } from '@vowel.to/client/react';

<VowelMicrophone 
  size="medium"
  variant="floating"
/>

Props:

  • size - 'small' | 'medium' | 'large' (default: 'medium')
  • variant - 'floating' | 'inline' (default: 'floating')
  • className - Additional CSS classes
  • style - Inline styles

Hooks

useVowel

Access the Vowel client and state:

tsx
import { useVowel } from '@vowel.to/client/react';

function MyComponent() {
  const { client, state } = useVowel();

  // Access client methods
  const startSession = () => client.startSession();
  const stopSession = () => client.stopSession();
  const notify = (msg: string) => client.notifyEvent(msg);

  // Access state
  console.log(state.isConnected);
  console.log(state.isUserSpeaking);
  console.log(state.isAISpeaking);
  console.log(state.isAIThinking);

  return (
    <div>
      {state.isConnected ? 'Connected' : 'Disconnected'}
    </div>
  );
}

Returns:

typescript
{
  client: Vowel;
  state: VoiceSessionState;
}

useSyncContext

Automatically sync context with the Vowel client. This hook updates the AI's context whenever the provided value changes, making it easy to keep the AI aware of dynamic application state.

tsx
import { useSyncContext } from '@vowel.to/client/react';

function ProductPage({ productId }: { productId: string }) {
  const product = useProduct(productId);
  
  // Automatically sync product context to Vowel
  useSyncContext({
    page: 'product',
    productId: product.id,
    productName: product.name,
    price: product.price,
    inStock: product.inStock
  });
  
  return <div>{product.name}</div>;
}

Parameters:

  • context - Context object to sync (Record<string, unknown> | null). Use null to clear context.

Features:

  • Automatically updates context when the value changes
  • Clears context on component unmount
  • Uses deep comparison to avoid unnecessary updates
  • Works seamlessly with React state and props

Example: Conditional Context

tsx
function UserProfile({ userId }: { userId: string | null }) {
  const user = useUser(userId);
  
  // Sync user context, or clear if no user
  useSyncContext(
    user ? {
      userId: user.id,
      userName: user.name,
      userRole: user.role
    } : null
  );
  
  return user ? <div>{user.name}</div> : <div>No user</div>;
}

Example: Page Context

tsx
function CheckoutPage() {
  const cart = useCart();
  
  // Sync cart context
  useSyncContext({
    page: 'checkout',
    cartTotal: cart.total,
    itemCount: cart.items.length,
    shippingAddress: cart.shippingAddress
  });
  
  return <div>Checkout</div>;
}

See the Dynamic Context recipe for more examples and patterns.

Common Patterns

Auto-Start Session

tsx
function App() {
  const { client } = useVowel();

  useEffect(() => {
    client.startSession();
    
    return () => {
      client.stopSession();
    };
  }, [client]);

  return <YourApp />;
}

Conditional Rendering Based on State

tsx
function VoiceStatus() {
  const { state } = useVowel();

  return (
    <div>
      {state.isConnected && (
        <div className="status-indicator">
          {state.isUserSpeaking && <span>🎤 Listening...</span>}
          {state.isAIThinking && <span>🤔 Thinking...</span>}
          {state.isAISpeaking && <span>🔊 Speaking...</span>}
        </div>
      )}
    </div>
  );
}

Event Notification on Action

tsx
function AddToCartButton({ productId }: { productId: string }) {
  const { client } = useVowel();

  const handleAddToCart = async () => {
    await addToCart(productId);
    
    // Notify via voice
    await client.notifyEvent('Item added to cart');
  };

  return (
    <button onClick={handleAddToCart}>
      Add to Cart
    </button>
  );
}

Custom Voice Action Hook

tsx
function useVoiceAction<T>(
  name: string,
  definition: VowelAction,
  handler: ActionHandler<T>
) {
  const { client } = useVowel();

  useEffect(() => {
    client.registerAction(name, definition, handler);
  }, [client, name, definition, handler]);
}

// Usage
function ProductSearch() {
  useVoiceAction('searchProducts', {
    description: 'Search for products',
    parameters: {
      query: { type: 'string', description: 'Search query' }
    }
  }, async ({ query }) => {
    const results = await searchProducts(query);
    return { success: true, data: results };
  });

  return <SearchResults />;
}

Voice Notification Hook

tsx
function useVoiceNotification() {
  const { client } = useVowel();

  const notify = useCallback(async (message: string, context?: any) => {
    if (client.state.isConnected) {
      await client.notifyEvent(message, context);
    }
  }, [client]);

  return { notify };
}

// Usage
function OrderConfirmation({ order }: { order: Order }) {
  const { notify } = useVoiceNotification();

  useEffect(() => {
    notify('Order confirmed!', { orderId: order.id });
  }, [order, notify]);

  return <div>Order #{order.id} confirmed!</div>;
}

Complete Example

tsx
// App.tsx
import { VowelProvider, VowelAgent, useVowel } from '@vowel.to/client/react';
import { Vowel, createDirectAdapters } from '@vowel.to/client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

// Create client
function createVowelClient(router: any) {
  const { navigationAdapter, automationAdapter } = createDirectAdapters({
    navigate: (path) => router.push(path),
    routes: [
      { path: '/', description: 'Home' },
      { path: '/products', description: 'Products' },
      { path: '/cart', description: 'Cart' }
    ],
    enableAutomation: true
  });

  const vowel = new Vowel({
    appId: 'your-app-id',
    navigationAdapter,
    automationAdapter
  });

  // Register actions
  vowel.registerAction('addToCart', {
    description: 'Add product to cart',
    parameters: {
      productId: { type: 'string', required: true }
    }
  }, async ({ productId }) => {
    await addToCart(productId);
    return { success: true };
  });

  return vowel;
}

// Main app component
function AppContent() {
  const { client, state } = useVowel();

  useEffect(() => {
    // Auto-start session
    client.startSession();

    return () => {
      client.stopSession();
    };
  }, [client]);

  return (
    <div>
      <header>
        <h1>My Store</h1>
        {state.isConnected && (
          <div className="voice-status">
            Voice: {state.isAISpeaking ? 'Speaking' : 'Ready'}
          </div>
        )}
      </header>
      
      <main>
        <YourContent />
      </main>

      {/* Voice agent UI */}
      <VowelAgent position="bottom-right" />
    </div>
  );
}

// Root component
export default function App() {
  const router = useRouter();
  const vowel = createVowelClient(router);

  return (
    <VowelProvider client={vowel}>
      <AppContent />
    </VowelProvider>
  );
}

TypeScript Support

Full TypeScript support with type definitions:

tsx
import type { 
  VowelProviderProps,
  VowelAgentProps,
  VowelMicrophoneProps,
  VowelContextType
} from '@vowel.to/client/react';

// Typed component
const MyComponent: React.FC = () => {
  const { client, state }: VowelContextType = useVowel();
  
  return <div>{state.isConnected && 'Connected'}</div>;
};

Styling

Custom Styles

tsx
<VowelAgent 
  position="bottom-right"
  style={{
    '--vowel-primary-color': '#007bff',
    '--vowel-background': '#ffffff',
    '--vowel-border-radius': '12px'
  } as React.CSSProperties}
/>

CSS Classes

tsx
<VowelAgent 
  position="bottom-right"
  className="my-custom-agent"
/>
css
.my-custom-agent {
  /* Your custom styles */
}