Skip to content

Answer the quiz by voice

Goal: on a kiosk or for a shopper who cannot tap, a spoken word picks an option and the quiz moves on; “skip” and “back” work too.

Uses: Storefront JS API, page.viewed, prefill(), navigate(), and the browser’s SpeechRecognition. Nothing to install; no API key. The methods work on every plan; page.viewed fires on the Plus and Enterprise plans.

SpeechRecognition (prefixed webkitSpeechRecognition) exists in Chrome, Edge and Safari; Firefox does not ship it. Chrome and Edge send the audio to a cloud service, Safari to Apple’s, so it needs a network and a page served over HTTPS, and the microphone prompt only opens after a user gesture. The page below shows a “Talk to answer” button and leaves the quiz as it is where the API is missing.

The quiz needs an option’s value, not the word the shopper said, so the page keeps a map per question: component_key in the editor to { spoken word: option value }. Recognition starts on each page.viewed and stops once a word matched.

<button id="talk" hidden>Talk to answer</button>
<script>
window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
var Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!Recognition) return;
var WORDS = {
'image_choice-48su5': { dry: 'dry', oily: 'oily', combination: 'combination', normal: 'normal' },
'multiple_choice-goals': { glow: ['glow'], hydration: ['hydration'], 'anti aging': ['anti_aging'] }
};
var COMMANDS = { skip: 'skip', back: 'back', next: 'next' };
var listening = false;
var button = document.getElementById('talk');
var recognition = new Recognition();
recognition.continuous = true;
recognition.interimResults = false;
recognition.lang = document.documentElement.lang || 'en-US';
recognition.onresult = function (e) {
var heard = e.results[e.results.length - 1][0].transcript.trim().toLowerCase();
if (COMMANDS[heard]) { octaneai.navigate(COMMANDS[heard]); return; }
Object.keys(WORDS).some(function (componentKey) {
var word = Object.keys(WORDS[componentKey]).find(function (w) { return heard.indexOf(w) !== -1; });
if (!word) return false;
var values = {};
values[componentKey] = WORDS[componentKey][word];
octaneai.prefill(values);
octaneai.navigate('next');
return true;
});
};
recognition.onend = function () { if (listening) recognition.start(); };
recognition.onerror = function (e) {
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') { listening = false; button.textContent = 'Microphone blocked'; }
};
octaneai.ready(function () { button.hidden = false; });
button.addEventListener('click', function () {
listening = !listening;
button.textContent = listening ? 'Listening... (tap to stop)' : 'Talk to answer';
if (listening) recognition.start(); else recognition.stop();
});
octaneai.on('quiz.finished', function () { listening = false; recognition.stop(); });
});
</script>

prefill stages the option’s value into the current page; navigate('next') submits it right after, the same way a tap on the option and then on Next would. The match runs against every mapped question, so a word only answers the page it belongs to when the option values are distinct per question; use page_key from getState() to narrow the map when two pages share words.

  • recognition.start() throws InvalidStateError when it is already running; the onend restart above only starts after a stop, so the toggle stays in step.
  • onerror with not-allowed means the microphone permission was refused; network means the speech service is unreachable. no-speech and aborted are normal and restart through onend.
  • A prefilled value the question does not have is refused when the page is submitted (page.rejected, reason: 'invalid'), so a wrong entry in WORDS shows as a validation message, not a crash.
  • navigate('next') on a page with an unanswered required question fires page.rejected and stays; say “skip” to move on without answering.
  • Text, email and phone questions are not in the map on purpose: dictating an email address in a shop is neither reliable nor private. Tap those.
  • Speech recognition is per tab; a quiz opened in a new tab needs its own button press.
  • A multi-select question takes an array (['glow'] above, for one pick); map a phrase to ['glow', 'hydration'] and prefill stages both. A single-select takes the bare option value.
  • A kiosk that should listen without a press each time can keep listening true across page loads in sessionStorage, but the first start() after a load still needs a gesture in every browser.