20 / 28
All 28 in 28 Tailwind CSS Input Fields
- 01Tailwind Floating Label Input
- 02Tailwind Underline Sweep Input
- 03Tailwind Neon Glow Input
- 04Tailwind Frosted Glass Input
- 05Tailwind Notched Outline Input
- 06Tailwind Drawn Border Input
- 07Tailwind Inline Validation Input
- 08Tailwind Search Input With Clear
- 09Tailwind Password Strength Meter
- 10Tailwind OTP Code Input
- 11Tailwind Rotating Placeholder Input
- 12Tailwind Tag Chip Input
- 13Tailwind Phone Input With Country
- 14Tailwind Stepper Number Input
- 15Tailwind File Drop Zone
- 16Tailwind Currency Input
- 17Tailwind Native Date Input
- 18Tailwind Time Range Input
- 19Tailwind Colour Picker Input
- 20Tailwind Voice Search Input
- 21Tailwind Email Suggestion Combobox
- 22Tailwind Auto-Grow Textarea
- 23Tailwind Inline Edit Field
- 24Tailwind Range Slider
- 25Tailwind URL Input With Protocol
- 26Tailwind Credit Card Input
- 27Tailwind Password Toggle Input
- 28Tailwind Brutalist Input
Tailwind Voice Search Input
A search field with a mic that pulses while listening. The recording state is aria-pressed on a real button, and every visual — icon colour, halo, pulse — is a variant reading that attribute, so the accessible state and the visible state cannot disagree.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tif-20 grid min-h-svh w-full place-items-center bg-tif20-bg px-6 py-14 font-tif20-sans" aria-label="Voice search input demo">
<form role="search" autocomplete="off" class="grid w-full max-w-80 gap-1.5">
<label for="tif20-input" class="font-tif20-mono text-[10px] tracking-[0.12em] text-tif20-mut uppercase">Voice search</label>
<span class="flex items-center gap-2 rounded-[10px] border border-white/12 bg-tif20-field py-1 pr-1 pl-3.5 transition-colors duration-200 focus-within:border-tif20-rec">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 shrink-0 fill-none stroke-tif20-dim stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="tif20-input" type="search" name="q" inputmode="search" placeholder="Speak or type to search…"
class="min-w-0 flex-1 border-0 bg-transparent py-2 text-sm font-medium text-tif20-ink outline-none placeholder:text-tif20-mut">
<button type="button" aria-label="Start voice input" aria-pressed="false" data-tif20-mic
class="group relative grid size-11 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent transition-colors duration-200 hover:bg-tif20-rec/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tif20-rec aria-pressed:bg-tif20-rec/10 aria-pressed:after:absolute aria-pressed:after:inset-2 aria-pressed:after:rounded-md aria-pressed:after:border-[1.5px] aria-pressed:after:border-tif20-rec aria-pressed:after:animate-tif20-pulse aria-pressed:after:content-[''] motion-reduce:aria-pressed:after:animate-none">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 fill-none stroke-tif20-acc stroke-2 transition-colors [stroke-linecap:round] group-aria-pressed:stroke-tif20-rec"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>
</button>
</span>
</form>
</div><div class="tif-20 grid min-h-svh w-full place-items-center bg-tif20-bg px-6 py-14 font-tif20-sans" aria-label="Voice search input demo">
<form role="search" autocomplete="off" class="grid w-full max-w-80 gap-1.5">
<label for="tif20-input" class="font-tif20-mono text-[10px] tracking-[0.12em] text-tif20-mut uppercase">Voice search</label>
<span class="flex items-center gap-2 rounded-[10px] border border-white/12 bg-tif20-field py-1 pr-1 pl-3.5 transition-colors duration-200 focus-within:border-tif20-rec">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 shrink-0 fill-none stroke-tif20-dim stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="tif20-input" type="search" name="q" inputmode="search" placeholder="Speak or type to search…"
class="min-w-0 flex-1 border-0 bg-transparent py-2 text-sm font-medium text-tif20-ink outline-none placeholder:text-tif20-mut">
<button type="button" aria-label="Start voice input" aria-pressed="false" data-tif20-mic
class="group relative grid size-11 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent transition-colors duration-200 hover:bg-tif20-rec/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tif20-rec aria-pressed:bg-tif20-rec/10 aria-pressed:after:absolute aria-pressed:after:inset-2 aria-pressed:after:rounded-md aria-pressed:after:border-[1.5px] aria-pressed:after:border-tif20-rec aria-pressed:after:animate-tif20-pulse aria-pressed:after:content-[''] motion-reduce:aria-pressed:after:animate-none">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 fill-none stroke-tif20-acc stroke-2 transition-colors [stroke-linecap:round] group-aria-pressed:stroke-tif20-rec"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>
</button>
</span>
</form>
</div>@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}(function () {
document.querySelectorAll('.tif-20').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
root.querySelectorAll('[data-tif20-mic]').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');
// Real recognition goes here:
// var Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
});
});
});
})();(function () {
document.querySelectorAll('.tif-20').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
root.querySelectorAll('[data-tif20-mic]').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');
// Real recognition goes here:
// var Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
});
});
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
@keyframesstays in CSS — it has to — but it is registered as--animate-tif20-pulsein@theme, so the markup saysanimate-tif20-pulselike any other utility. - Three
[aria-pressed="true"]descendant rules becomearia-pressed:on the button andgroup-aria-pressed:on the icon. - The mic button went 36px →
size-11, and the@media (prefers-reduced-motion)block with its!importantbecame onemotion-reduce:variant.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Voice Search Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-voice-search-input/
A search field with a mic that pulses while listening. The recording state is aria-pressed on a real button, and every visual — icon colour, halo, pulse — is a variant reading that attribute, so the accessible state and the visible state cannot disagree.
## HTML
```html
<div class="tif-20 grid min-h-svh w-full place-items-center bg-tif20-bg px-6 py-14 font-tif20-sans" aria-label="Voice search input demo">
<form role="search" autocomplete="off" class="grid w-full max-w-80 gap-1.5">
<label for="tif20-input" class="font-tif20-mono text-[10px] tracking-[0.12em] text-tif20-mut uppercase">Voice search</label>
<span class="flex items-center gap-2 rounded-[10px] border border-white/12 bg-tif20-field py-1 pr-1 pl-3.5 transition-colors duration-200 focus-within:border-tif20-rec">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 shrink-0 fill-none stroke-tif20-dim stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="tif20-input" type="search" name="q" inputmode="search" placeholder="Speak or type to search…"
class="min-w-0 flex-1 border-0 bg-transparent py-2 text-sm font-medium text-tif20-ink outline-none placeholder:text-tif20-mut">
<button type="button" aria-label="Start voice input" aria-pressed="false" data-tif20-mic
class="group relative grid size-11 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent transition-colors duration-200 hover:bg-tif20-rec/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tif20-rec aria-pressed:bg-tif20-rec/10 aria-pressed:after:absolute aria-pressed:after:inset-2 aria-pressed:after:rounded-md aria-pressed:after:border-[1.5px] aria-pressed:after:border-tif20-rec aria-pressed:after:animate-tif20-pulse aria-pressed:after:content-[''] motion-reduce:aria-pressed:after:animate-none">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 fill-none stroke-tif20-acc stroke-2 transition-colors [stroke-linecap:round] group-aria-pressed:stroke-tif20-rec"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>
</button>
</span>
</form>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tif-20').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
root.querySelectorAll('[data-tif20-mic]').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');
// Real recognition goes here:
// var Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
});
});
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Voice Search Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-voice-search-input/
A search field with a mic that pulses while listening. The recording state is aria-pressed on a real button, and every visual — icon colour, halo, pulse — is a variant reading that attribute, so the accessible state and the visible state cannot disagree.
## HTML
```html
<div class="tif-20 grid min-h-svh w-full place-items-center bg-tif20-bg px-6 py-14 font-tif20-sans" aria-label="Voice search input demo">
<form role="search" autocomplete="off" class="grid w-full max-w-80 gap-1.5">
<label for="tif20-input" class="font-tif20-mono text-[10px] tracking-[0.12em] text-tif20-mut uppercase">Voice search</label>
<span class="flex items-center gap-2 rounded-[10px] border border-white/12 bg-tif20-field py-1 pr-1 pl-3.5 transition-colors duration-200 focus-within:border-tif20-rec">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 shrink-0 fill-none stroke-tif20-dim stroke-2 [stroke-linecap:round]"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="tif20-input" type="search" name="q" inputmode="search" placeholder="Speak or type to search…"
class="min-w-0 flex-1 border-0 bg-transparent py-2 text-sm font-medium text-tif20-ink outline-none placeholder:text-tif20-mut">
<button type="button" aria-label="Start voice input" aria-pressed="false" data-tif20-mic
class="group relative grid size-11 shrink-0 cursor-pointer place-items-center rounded-lg border-0 bg-transparent transition-colors duration-200 hover:bg-tif20-rec/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tif20-rec aria-pressed:bg-tif20-rec/10 aria-pressed:after:absolute aria-pressed:after:inset-2 aria-pressed:after:rounded-md aria-pressed:after:border-[1.5px] aria-pressed:after:border-tif20-rec aria-pressed:after:animate-tif20-pulse aria-pressed:after:content-[''] motion-reduce:aria-pressed:after:animate-none">
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-4 fill-none stroke-tif20-acc stroke-2 transition-colors [stroke-linecap:round] group-aria-pressed:stroke-tif20-rec"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>
</button>
</span>
</form>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif20-bg: #12121a;
--color-tif20-bg: oklch(0.19 0.015 285);
--color-tif20-field: #1a1a22;
--color-tif20-field: oklch(0.23 0.015 285);
--color-tif20-ink: #f0eeff;
--color-tif20-ink: oklch(0.95 0.02 290);
--color-tif20-mut: #b8b6d4;
--color-tif20-mut: oklch(0.77 0.03 290);
--color-tif20-dim: #6b6987;
--color-tif20-dim: oklch(0.55 0.03 290);
--color-tif20-acc: #a78bfa;
--color-tif20-acc: oklch(0.74 0.15 295);
--color-tif20-rec: #ff6c8a;
--color-tif20-rec: oklch(0.72 0.17 15);
--font-tif20-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tif20-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tif20-pulse: tif20-pulse 1.2s ease-out infinite;
}
/* The keyframes the @theme animation token points at — the one thing that cannot
be a utility. */
@keyframes tif20-pulse {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.tif-20 {
width: 100%;
}
.tif-20 ::selection {
background: var(--color-tif20-rec);
color: #12121a;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tif-20').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
root.querySelectorAll('[data-tif20-mic]').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');
// Real recognition goes here:
// var Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
});
});
});
})();
```How this works
ARIA is the state store. The script toggles one attribute and rewrites the label; CSS does the rest:
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');Wiring real recognition. Instantiate SpeechRecognition (or webkitSpeechRecognition) inside the click handler, set continuous=false, and write the transcript back on the result event. It needs a user gesture, HTTPS and microphone permission — the same three constraints behind every mic button you have ever used.
Make it yours
- Language:
rec.lang = 'en-GB'or any BCP-47 tag. - Dictation:
continuous = truefor multi-utterance input. - Accent:
--color-tif20-recdrives the recording icon, halo and focus border. - Icon: swap the mic SVG for a waveform and drive it from the
audiostartevent.
Gotchas — read before shipping
- Firefox has no Web Speech support. Feature-detect and hide the mic rather than shipping a dead button.
- The API only works on HTTPS or localhost; on plain HTTP the constructor exists and
start()throws. - Recognition stops itself after silence — handle
endand resetaria-pressed, or the UI claims to be listening when it is not.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | not supported | 111+ |
Web Speech is prefixed as webkitSpeechRecognition in Chrome and Safari and absent in Firefox; the UI itself renders everywhere.