> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ais-voice-search

> Lets users submit search queries using voice input.

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

export const FlavorSwitcher = ({current, baseHref = "", options = [], label = "InstantSearch framework"}) => {
  if (options.length === 0) {
    return <div className="not-prose" role="alert" style={{
      margin: "0.25rem 0 1.5rem",
      padding: "0.75rem",
      border: "1px solid #f59e0b",
      borderRadius: "0.625rem",
      color: "inherit",
      fontSize: "0.875rem"
    }}>
        FlavorSwitcher requires at least one option.
      </div>;
  }
  const selected = options.find(option => option.value === current) ?? options[0];
  return <div className="not-prose mint-flavor-switcher">
      <style>{`
        .mint-flavor-switcher {
          --mfs-bg: #ffffff;
          --mfs-bg-hover: #f4f4f5;
          --mfs-bg-current: #eef2ff;
          --mfs-border: #d4d4d8;
          --mfs-fg: #18181b;
          --mfs-muted: #71717a;
          --mfs-accent: #4f46e5;
          position: relative;
          width: min(100%, 19rem);
          margin: 0.25rem 0 1.5rem;
          color: var(--mfs-fg);
          font-size: 0.875rem;
          line-height: 1.25rem;
        }

        .dark .mint-flavor-switcher {
          --mfs-bg: #18181b;
          --mfs-bg-hover: #27272a;
          --mfs-bg-current: #272747;
          --mfs-border: #3f3f46;
          --mfs-fg: #fafafa;
          --mfs-muted: #a1a1aa;
          --mfs-accent: #a5b4fc;
        }

        .mint-flavor-switcher details {
          position: relative;
        }

        .mint-flavor-switcher summary {
          display: flex;
          min-height: 2.75rem;
          box-sizing: border-box;
          align-items: center;
          justify-content: space-between;
          gap: 0.75rem;
          padding: 0.625rem 0.75rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          color: var(--mfs-fg);
          cursor: pointer;
          font-weight: 600;
          list-style: none;
          transition: border-color 150ms ease, box-shadow 150ms ease;
        }

        .mint-flavor-switcher summary::-webkit-details-marker {
          display: none;
        }

        .mint-flavor-switcher summary:hover {
          border-color: var(--mfs-accent);
        }

        .mint-flavor-switcher summary:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: 2px;
        }

        .mint-flavor-switcher__label {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .mint-flavor-switcher__chevron {
          flex: none;
          transition: transform 150ms ease;
        }

        .mint-flavor-switcher details[open] .mint-flavor-switcher__chevron {
          transform: rotate(180deg);
        }

        .mint-flavor-switcher__menu {
          position: absolute;
          z-index: 50;
          top: calc(100% + 0.375rem);
          left: 0;
          width: 100%;
          box-sizing: border-box;
          margin: 0;
          padding: 0.375rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          box-shadow: 0 12px 30px rgb(0 0 0 / 16%);
          list-style: none;
        }

        .mint-flavor-switcher__menu li {
          margin: 0;
          padding: 0;
        }

        .mint-flavor-switcher__option {
          display: grid;
          gap: 0.125rem;
          padding: 0.625rem 0.75rem;
          border-radius: 0.4rem;
          color: var(--mfs-fg);
          text-decoration: none;
        }

        .mint-flavor-switcher__option:hover {
          background: var(--mfs-bg-hover);
        }

        .mint-flavor-switcher__option:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: -2px;
        }

        .mint-flavor-switcher__option[aria-current="page"] {
          background: var(--mfs-bg-current);
          color: var(--mfs-accent);
        }

        .mint-flavor-switcher__name {
          font-weight: 600;
        }

        .mint-flavor-switcher__description {
          color: var(--mfs-muted);
          font-size: 0.8125rem;
        }

        @media (prefers-reduced-motion: reduce) {
          .mint-flavor-switcher summary,
          .mint-flavor-switcher__chevron {
            transition: none;
          }
        }
      `}</style>

      <details>
        <summary aria-label={`${label}: ${selected.label}`}>
          <span className="mint-flavor-switcher__label">{selected.label}</span>
          <svg className="mint-flavor-switcher__chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </summary>

        <ul className="mint-flavor-switcher__menu" aria-label={label}>
          {options.map(option => {
    const isCurrent = option.value === selected.value;
    const href = option.href ?? `${baseHref.replace(/\/$/, "")}/${encodeURIComponent(option.value)}`;
    return <li key={option.value}>
                <a className="mint-flavor-switcher__option" href={href} aria-current={isCurrent ? "page" : undefined}>
                  <span className="mint-flavor-switcher__name">
                    {option.label}
                  </span>
                  {option.description ? <span className="mint-flavor-switcher__description">
                      {option.description}
                    </span> : null}
                </a>
              </li>;
  })}
        </ul>
      </details>
    </div>;
};

<div className="mint-flavor-switcher-slot not-prose">
  <FlavorSwitcher
    current="vue"
    baseHref="/doc/api-reference/widgets/voice-search"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
{ value: "vue", label: "Vue", description: "Vue InstantSearch" },
]}
  />
</div>

```vue Signature theme={"system"}
<ais-voice-search
  // Optional parameters
  :search-as-you-speak="boolean"
  :button-title="string"
  :disabled-button-title="string"
  :class-names="object"
/>
```

## Import

<Tabs>
  <Tab title="Component">
    To ensure optimal bundle sizes,
    see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size).

    ```js Vue icon=code theme={"system"}
    import { AisVoiceSearch } from "vue-instantsearch";
    // Use "vue-instantsearch/vue3/es" for Vue 3

    export default {
      components: {
        AisVoiceSearch,
      },
      // ...
    };
    ```
  </Tab>

  <Tab title="Plugin">
    This imports all widgets, even the ones you don't use.
    For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue).

    ```js JavaScript icon="code" theme={"system"}
    import Vue from "vue";
    import InstantSearch from "vue-instantsearch";
    // Use "vue-instantsearch/vue3/es" for Vue 3

    Vue.use(InstantSearch);
    ```
  </Tab>
</Tabs>

<Card title="See this widget in action" icon="monitor-play" href="https://instantsearchjs.netlify.app/stories/vue/?selectedKind=ais-voice-search" horizontal>
  Preview this widget and its behavior.
</Card>

## About this widget

The `ais-voice-search` widget lets users perform a voice-based <SearchQuery />.

It uses the [Web Speech API](https://w3c.github.io/speech-api),
which only Chrome (from version 25) has implemented so far.
This means the `voiceSearch` widget only works on desktop Chrome and Android Chrome.
It doesn't work on iOS Chrome, which uses the iOS WebKit.

## Examples

```vue Vue icon=code theme={"system"}
<ais-voice-search />
```

## Props

<ParamField body="search-as-you-speak" type="boolean" default={false}>
  Whether to trigger the search as you speak.
  If `false`, search is triggered only after speech is finished.
  If `true`, search is triggered whenever the engine delivers an interim transcript.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search search-as-you-speak />
  ```
</ParamField>

<ParamField body="button-title" type="string" default="'Search by voice'">
  The `title` attribute on the button.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search button-title="Voice Search" />
  ```
</ParamField>

<ParamField body="disabled-button-title" type="string" default="'Search by voice (not supported on this browser)'">
  The `title` attribute on the button when it's disabled on unsupported browsers.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search disabled-button-title="Voice Search Disabled" />
  ```
</ParamField>

<ParamField body="class-names" type="object" default="{}">
  The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets):

  * `ais-VoiceSearch`. The root element of the widget.
  * `ais-VoiceSearch-button`. The button element.
  * `ais-VoiceSearch-status`. The status element.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search
    :class-names="{
      'ais-VoiceSearch': 'MyVoiceSearch',
      'ais-VoiceSearch-button': 'MyVoiceSearchButton',
      'ais-VoiceSearch-status': 'MyVoiceSearchStatus'
    }"
  />
  ```
</ParamField>

## Customize the UI

<ParamField body="default">
  The slot to override the complete DOM output of the widget.

  When you implement this slot, none of the other slots will change the output, as the default slot surrounds them.

  **Scope**

  * `isBrowserSupported: boolean`. `true` if user's browser supports voice search.
  * `isListening: boolean`. `true` if listening to user's speech.
  * `toggleListening: () => void`. Starts listening to user's speech, or stops it if already listening.
  * `voiceListeningState: object`. An object containing the following states regarding speech recognition:
    * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`).
    * `transcript: string`. Currently recognized transcript.
    * `isSpeechFinal: boolean`. `true` if speech recognition is finished.
    * `errorCode: string | undefined`. An error code (if any).
      Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search>
    <template v-slot="{
        isBrowserSupported,
        isListening,
        toggleListening,
        voiceListeningState,
    }">
      <button @click="toggleListening">click</button>
      <p>isListening: {{ isListening ? 'true' : 'false' }}</p>
      <p>isBrowserSupported: {{ isBrowserSupported ? 'true' : 'false' }}</p>
      <pre>voiceListeningState: {{
        JSON.stringify(voiceListeningState, null, 2)
      }}</pre>
    </template>
  </ais-voice-search>
  ```
</ParamField>

<ParamField body="buttonText">
  The slot to override the DOM output inside the button.

  **Scope**

  * `isListening: boolean`. `true` if listening to user's speech.
  * `isBrowserSupported: boolean`. `true` if user's browser supports voice search.
  * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`).
  * `errorCode: string | undefined`. An error code (if any).
    Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information.
  * `transcript: string`. Currently recognized transcript.
  * `isSpeechFinal: boolean`. `true` if speech recognition is finished.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search>
    <template v-slot:buttonText="{ isListening }">
      {{ isListening ? 'Stop' : 'Start' }}
    </template>
  </ais-voice-search>
  ```
</ParamField>

<ParamField body="status">
  The slot to override the DOM output of the status.

  **Scope**

  * `isListening: boolean`. `true` if listening to user's speech.
  * `isBrowserSupported: boolean`. `true` if user's browser supports voice search.
  * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`).
  * `errorCode: string | undefined`. An error code (if any).
    Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information.
  * `transcript: string`. Currently recognized transcript.
  * `isSpeechFinal: boolean`. `true` if speech recognition is finished.

  ```vue Vue icon=code theme={"system"}
  <ais-voice-search>
    <template v-slot:status="{ status, transcript }">
      <p v-if="status == 'initial'">Press the button to start speaking.</p>
      <p v-else>Searching for {{ transcript }}</p>
    </template>
  </ais-voice-search>
  ```
</ParamField>

## HTML output

```html HTML icon=code-xml theme={"system"}
<div class="ais-VoiceSearch">
  <button class="ais-VoiceSearch-button" type="button" title="Search by voice">
    ...
  </button>
  <div class="ais-VoiceSearch-status">...</div>
</div>
```
