npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

voice-form-agent

v0.1.5

Published

Convert any form into a voice conversation powered by Vapi AI

Readme

voice-form-agent

Feedback & Contributions Welcome! This package is actively evolving and I'd love to hear from you. If you run into issues, have ideas for improvements, or want to contribute, please open an issue or PR on GitHub. All feedback — big or small — is appreciated.

Convert any form into a voice conversation powered by Vapi AI.

Instead of typing, users click a button and speak their answers. The AI asks each question naturally, confirms answers, and submits the form automatically when done.


Installation

npm install voice-form-agent @vapi-ai/web
yarn add voice-form-agent @vapi-ai/web

@vapi-ai/web is a required peer dependency. If you hit issues with older versions, add this to your project's package.json:

"overrides": {
  "@daily-co/daily-js": "0.90.0"
}

Requires React 17 or later.


Quick start

import { VoiceFormAgent } from 'voice-form-agent'

export default function ContactPage() {
  return (
    <VoiceFormAgent
      vapiKey="your-vapi-public-key"
      fields={[
        { id: 'name',    label: 'Full Name' },
        { id: 'email',   label: 'Email Address', type: 'email' },
        { id: 'message', label: 'Tell us about your project', type: 'textarea' },
      ]}
      onComplete={(data) => {
        console.log(data)
        // { name: "John Smith", email: "[email protected]", message: "..." }
      }}
    />
  )
}

Your Vapi public key is available in the Vapi Dashboard under API Keys.


How it works

  1. User clicks 🎙️ Start Voice Form
  2. Vapi starts a call with a dynamically generated assistant configured from your fields
  3. The assistant greets the user and asks each question one at a time
  4. When all answers are collected, it calls an internal submitForm tool
  5. onComplete fires with a structured object containing all field values
  6. The button updates to a ✓ Form submitted badge

Props

| Prop | Type | Required | Default | Description | |---|---|---|---|---| | vapiKey | string | Yes | — | Your Vapi public API key | | fields | FormField[] | Yes | — | List of fields to collect | | onComplete | (data: FormData) => void | No | — | Called with collected answers when done | | onError | (error: Error) => void | No | — | Called if Vapi encounters an error | | assistantName | string | No | "Form Assistant" | Name the AI introduces itself as | | firstMessage | string | No | "Hi! I'll help you fill out this form. Let's get started." | Opening line the assistant speaks | | voice | VoiceConfig | No | { provider: 'openai', voiceId: 'alloy' } | Voice provider and voice ID | | model | AIModel | No | "gpt-4o-mini" | AI model for the assistant | | buttonLabel | string | No | "Start Voice Form" | Label on the start button | | stopLabel | string | No | "Stop" | Label on the stop button | | className | string | No | — | CSS class applied to the wrapper element |

FormField

| Property | Type | Required | Description | |---|---|---|---| | id | string | Yes | Key used in the onComplete data object | | label | string | Yes | Human-readable name — the AI uses this to ask the question | | type | FieldType | No | Hint for the AI (default: "text") | | required | boolean | No | Whether the field must be collected (default: true) |

FieldType: "text" "email" "phone" "number" "textarea"


Voices

The voice prop accepts a VoiceConfig object with a provider and voiceId.

OpenAI voices — no extra credentials needed

Works out of the box with any Vapi account.

voice={{ provider: 'openai', voiceId: 'alloy' }}   // default — neutral, versatile
voice={{ provider: 'openai', voiceId: 'echo' }}    // male, clear
voice={{ provider: 'openai', voiceId: 'fable' }}   // expressive, storyteller
voice={{ provider: 'openai', voiceId: 'onyx' }}    // deep, authoritative
voice={{ provider: 'openai', voiceId: 'nova' }}    // female, warm
voice={{ provider: 'openai', voiceId: 'shimmer' }} // female, soft

ElevenLabs voices — requires ElevenLabs credentials

Add your ElevenLabs API key in Vapi Dashboard → Credentials first.

voice={{ provider: '11labs', voiceId: 'sarah' }}    // female, natural
voice={{ provider: '11labs', voiceId: 'matilda' }}  // female, warm
voice={{ provider: '11labs', voiceId: 'andrea' }}   // female, clear
voice={{ provider: '11labs', voiceId: 'marissa' }}  // female, friendly
voice={{ provider: '11labs', voiceId: 'paula' }}    // female, professional
voice={{ provider: '11labs', voiceId: 'myra' }}     // female, calm
voice={{ provider: '11labs', voiceId: 'ryan' }}     // male, natural
voice={{ provider: '11labs', voiceId: 'drew' }}     // male, conversational
voice={{ provider: '11labs', voiceId: 'paul' }}     // male, deep
voice={{ provider: '11labs', voiceId: 'phillip' }}  // male, energetic
voice={{ provider: '11labs', voiceId: 'burt' }}     // male, gruff
voice={{ provider: '11labs', voiceId: 'mrb' }}      // male, dramatic
voice={{ provider: '11labs', voiceId: 'mark' }}     // male, clear
voice={{ provider: '11labs', voiceId: 'joseph' }}   // male, authoritative
voice={{ provider: '11labs', voiceId: 'steve' }}    // male, upbeat

You can also pass any custom ElevenLabs voice ID from your Voice Library:

voice={{ provider: '11labs', voiceId: 'YOUR_CUSTOM_VOICE_ID' }}

AI Models

The model prop accepts a string model name. All models use OpenAI as the provider.

| Model | Speed | Quality | Best for | |---|---|---|---| | "gpt-4o-mini" | Fast | Good | Default — most forms | | "gpt-4o" | Medium | Great | Complex multi-step forms | | "gpt-4.1-nano" | Fastest | Basic | Simple, short forms | | "gpt-4.1-mini" | Fast | Good | General use | | "gpt-4.1" | Medium | Best | High-stakes / long forms | | "gpt-3.5-turbo" | Fast | Basic | Budget option |

<VoiceFormAgent
  vapiKey="..."
  model="gpt-4o"
  fields={[...]}
/>

You can also pass any valid OpenAI model string accepted by Vapi.


Examples

Custom voice and model

<VoiceFormAgent
  vapiKey={process.env.NEXT_PUBLIC_VAPI_KEY!}
  assistantName="Aria"
  firstMessage="Hi! I'm Aria. I'll grab a few details so we can get back to you."
  voice={{ provider: 'openai', voiceId: 'nova' }}
  model="gpt-4o"
  fields={[
    { id: 'name',    label: 'Full Name' },
    { id: 'email',   label: 'Work Email', type: 'email' },
    { id: 'company', label: 'Company Name' },
    { id: 'budget',  label: 'Monthly Budget', type: 'number' },
    { id: 'details', label: 'What are you looking for?', type: 'textarea' },
  ]}
  onComplete={(data) => submitLeadToHubspot(data)}
/>

ElevenLabs voice

<VoiceFormAgent
  vapiKey={process.env.NEXT_PUBLIC_VAPI_KEY!}
  voice={{ provider: '11labs', voiceId: 'sarah' }}
  fields={[
    { id: 'name',  label: 'Full Name' },
    { id: 'email', label: 'Email Address', type: 'email' },
  ]}
  onComplete={(data) => console.log(data)}
/>

Use only the hook (headless)

If you want to build your own UI:

import { useVoiceForm } from 'voice-form-agent'

function MyCustomForm() {
  const { status, isMuted, volumeLevel, start, stop, toggleMute } = useVoiceForm({
    vapiKey: 'your-key',
    fields: [
      { id: 'name',  label: 'Full Name' },
      { id: 'email', label: 'Email', type: 'email' },
    ],
    assistantName: 'Form Assistant',
    firstMessage: "Hi, let's get started.",
    voice: { provider: 'openai', voiceId: 'nova' },
    model: 'gpt-4o-mini',
    onComplete: (data) => console.log(data),
  })

  return (
    <div>
      <p>Status: {status}</p>
      <button onClick={start} disabled={status !== 'idle'}>Start</button>
      <button onClick={stop}  disabled={status === 'idle'}>Stop</button>
      <button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
    </div>
  )
}

Status values

| Status | Meaning | |---|---| | idle | Not started | | connecting | Connecting to Vapi | | active | Call in progress | | completed | All fields collected, form submitted | | error | Something went wrong |


Styling

The component ships with minimal inline styles so it works out of the box without any CSS setup. To apply your own styles, use the className prop on the wrapper or target the data-voice-form-status attribute:

/* wrapper */
[data-voice-form-status] {
  gap: 16px;
}

/* only while active */
[data-voice-form-status="active"] {
  outline: 2px solid #6366f1;
  border-radius: 8px;
  padding: 8px;
}

Requirements

  • A Vapi account (free tier available)
  • Your public Vapi API key (safe to expose in the browser)
  • React 17+
  • @vapi-ai/web installed as a direct dependency

License

MIT