<main class="page">
<section class="dictate" aria-labelledby="dictate-title">
<div class="dictate-head">
<h1 class="dictate-title" id="dictate-title">Voice input</h1>
<label class="lang">
<span class="sr-only">Language</span>
<select id="lang">
<option value="en-US">English (US)</option>
<option value="en-GB">English (UK)</option>
<option value="es-ES">Español</option>
<option value="fr-FR">Français</option>
<option value="de-DE">Deutsch</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="ja-JP">日本語</option>
<option value="zh-CN">中文</option>
</select>
</label>
</div>
<label class="sr-only" for="transcript">Message</label>
<textarea id="transcript" rows="4" placeholder="Press the mic and start talking, or type here"></textarea>
<p class="interim" id="interim" aria-hidden="true"></p>
<div class="dictate-bar">
<p class="status" id="status" role="status">Press the mic to start dictating.</p>
<button type="button" class="btn-clear" id="clear">Clear</button>
<button type="button" class="mic" id="mic" aria-pressed="false" aria-label="Voice dictation">
<svg viewBox="0 0 24 24" aria-hidden="true">
<rect x="9" y="3" width="6" height="11" rx="3"></rect>
<path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M8.5 21h7"></path>
</svg>
</button>
</div>
</section>
</main>*, *::before, *::after {
box-sizing: border-box;
}
:root {
--page: #eef2f7;
--card: #ffffff;
--text: #0f172a;
--muted: #5b6678;
--line: #d5dce6;
--soft: #eef2f7;
--accent: #2563eb;
--live: #dc2626;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
background: var(--page);
color: var(--text);
}
.page {
display: grid;
grid-template-columns: minmax(0, 560px);
justify-content: center;
align-content: center;
min-height: 100vh;
padding: 24px 16px;
}
.dictate {
padding: 20px;
border-radius: 18px;
background: var(--card);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 12px 32px -12px rgba(15, 23, 42, 0.2);
}
.dictate-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.dictate-title {
margin: 0;
font-size: 1.0625rem;
}
select {
min-height: 36px;
padding: 4px 8px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--card);
color: var(--text);
font: inherit;
font-size: 0.875rem;
}
textarea {
display: block;
width: 100%;
min-height: 112px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--card);
color: var(--text);
font: inherit;
line-height: 1.55;
resize: vertical;
}
textarea::placeholder {
color: var(--muted);
}
select:focus-visible,
textarea:focus-visible,
.btn-clear:focus-visible,
.mic:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.interim {
min-height: 1.5em;
margin: 8px 2px 0;
color: var(--muted);
font-style: italic;
line-height: 1.5;
}
.dictate-bar {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.status {
flex: 1;
margin: 0;
font-size: 0.875rem;
color: var(--muted);
line-height: 1.45;
}
.status.is-error {
color: #b91c1c;
}
.btn-clear {
min-height: 40px;
padding: 6px 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: transparent;
color: var(--text);
font: inherit;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
}
.btn-clear:hover {
background: var(--soft);
}
.mic {
position: relative;
display: grid;
place-items: center;
flex-shrink: 0;
width: 52px;
height: 52px;
padding: 0;
border: 0;
border-radius: 50%;
background: var(--text);
color: var(--card);
cursor: pointer;
transition: background 0.2s ease;
}
.mic svg {
width: 24px;
height: 24px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.mic[aria-pressed="true"] {
background: var(--live);
color: #ffffff;
}
.mic[aria-pressed="true"]::after {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.45);
animation: rec 1.6s ease-out infinite;
}
.mic:disabled {
background: var(--line);
color: var(--muted);
cursor: not-allowed;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@keyframes rec {
to {
box-shadow: 0 0 0 14px rgba(220, 38, 38, 0);
}
}
@media (pointer: coarse) {
.btn-clear,
select {
min-height: 44px;
}
}
@media (prefers-color-scheme: dark) {
:root {
--page: #0b1120;
--card: #151e30;
--text: #e2e8f0;
--muted: #a3afc2;
--line: #334155;
--soft: #1e293b;
--accent: #60a5fa;
--live: #ef4444;
}
.status.is-error {
color: #fca5a5;
}
.mic[aria-pressed="true"] {
color: #0b1120;
}
}
@media (forced-colors: active) {
.mic {
border: 2px solid ButtonText;
}
.mic[aria-pressed="true"] {
forced-color-adjust: none;
border-color: Highlight;
background: Highlight;
color: HighlightText;
}
}
@media (prefers-reduced-motion: reduce) {
.mic[aria-pressed="true"]::after {
animation: none;
}
}const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const errors = {
"not-allowed": "Microphone access is blocked. Allow it in the site settings. Inside an iframe, the frame also needs microphone in its allow attribute.",
"service-not-allowed": "The browser's speech service is turned off or not allowed on this page.",
"audio-capture": "No microphone could be used. Check that one is connected and that this page is allowed to use it.",
"no-speech": "No speech was heard. Press the mic and try again.",
"network": "Speech recognition needs a network connection in this browser.",
"language-not-supported": "That language is not supported here. Pick another one."
};
const textarea = document.getElementById("transcript");
const interim = document.getElementById("interim");
const status = document.getElementById("status");
const mic = document.getElementById("mic");
const clearBtn = document.getElementById("clear");
const lang = document.getElementById("lang");
let recognition = null;
let failed = false;
function setStatus(text, isError) {
status.textContent = text;
status.classList.toggle("is-error", Boolean(isError));
}
function setListening(on) {
mic.setAttribute("aria-pressed", String(on));
lang.disabled = on;
}
// Add a finished phrase to the end of the text box
function append(text, language) {
const piece = text.trim();
if (!piece) return;
const end = textarea.value.length;
const noSpace = /^(ja|zh)\b/i.test(language) || !end || /\s$/.test(textarea.value);
textarea.setRangeText((noSpace ? "" : " ") + piece, end, end, "preserve");
textarea.scrollTop = textarea.scrollHeight;
}
function start() {
const rec = new Recognition();
recognition = rec;
failed = false;
rec.lang = lang.value;
rec.continuous = true;
rec.interimResults = true;
setListening(true);
setStatus("Starting. Allow the microphone if the browser asks.");
// Events from an older session are ignored
rec.onstart = function() {
if (recognition !== rec) return;
setStatus("Listening. Press the mic again or Esc to stop.");
};
// Final results go into the box, the rest is a preview
rec.onresult = function(e) {
if (recognition !== rec) return;
let preview = "";
for (let i = e.resultIndex; i < e.results.length; i++) {
const result = e.results[i];
if (result.isFinal) {
append(result[0].transcript, rec.lang);
} else {
preview += result[0].transcript;
}
}
interim.textContent = preview;
};
rec.onerror = function(e) {
if (recognition !== rec || e.error === "aborted") return;
failed = true;
setStatus(errors[e.error] || "Speech recognition stopped with the error " + e.error + ".", true);
};
rec.onend = function() {
if (recognition !== rec) return;
recognition = null;
setListening(false);
interim.textContent = "";
if (!failed) setStatus("Stopped. Press the mic to keep dictating.");
};
try {
rec.start();
} catch (err) {
recognition = null;
setListening(false);
setStatus("Speech recognition could not start. " + err.message, true);
}
}
function stop() {
if (recognition) recognition.stop();
}
if (!Recognition) {
mic.disabled = true;
setStatus("Speech recognition is not available in this browser. It works in Chrome, Edge and Safari.", true);
}
mic.addEventListener("click", function() {
if (recognition) {
stop();
} else {
start();
}
});
clearBtn.addEventListener("click", function() {
textarea.value = "";
interim.textContent = "";
textarea.focus();
});
document.addEventListener("keydown", function(e) {
if (e.key === "Escape" && recognition) stop();
});Click any element to find its code Esc
Check this code before you run it
It came from a shared link. It runs with fewer permissions, so it cannot open pop-ups, submit forms or start downloads.
element.animate() call shows up here as it plays.