20 / 20
All 20 in 20 Tailwind CSS Custom Selects
- 01Tailwind Country Selector Dropdown With Flags
- 02Tailwind Currency Picker Dropdown With Symbols
- 03Tailwind Credit Card Type Selector Dropdown
- 04Tailwind Pricing Plan Switcher Dropdown
- 05Tailwind User Avatar Profile Dropdown Menu
- 06Tailwind Minimalist Language Switcher Dropdown
- 07Tailwind Account Workspace Switcher Dropdown
- 08Tailwind Dark Mode Theme Selector Dropdown
- 09Tailwind Multi-Select Tag Dropdown With Chips
- 10Tailwind Status Filter Badge Dropdown Select
- 11Tailwind Date Range Time Period Picker Dropdown
- 12Tailwind Product Sorting Filter Dropdown
- 13Tailwind Dropdown With Subtext And Descriptions
- 14Tailwind Font Family Preview Picker Dropdown
- 15Tailwind File Type Media Uploader Dropdown
- 16Tailwind Color Swatch Picker Dropdown
- 17Tailwind Utility First Select Dropdown
- 18Tailwind Animated Slide Fade Dropdown Transition
- 19Tailwind Floating Label Select Dropdown Field
- 20Tailwind Glassmorphism Dark UI Dropdown Select
Tailwind Glassmorphism Dark UI Dropdown Select
The showpiece: a frosted select floating over an aurora that drifts behind it. --animate-tcs20-drift in @theme mints animate-tcs20-drift, the three-blob backdrop is a single arbitrary bg-[radial-gradient(...),...], and the only CSS left is the keyframes and Safari's mask shim.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcs-20 relative w-full min-h-svh grid place-items-center overflow-hidden bg-tcs20-bg font-tcs20-sans text-tcs20-ink px-5 py-15" aria-label="Glassmorphism dark UI dropdown demo">
<div aria-hidden="true" class="absolute -inset-[20%] z-0 blur-[20px] animate-tcs20-drift motion-reduce:animate-none bg-[radial-gradient(40%_40%_at_25%_30%,oklch(0.6_0.24_303/0.55),transparent_70%),radial-gradient(45%_45%_at_75%_65%,oklch(0.75_0.14_230/0.5),transparent_70%),radial-gradient(35%_35%_at_60%_20%,oklch(0.65_0.22_352/0.4),transparent_70%)]"></div>
<div class="relative z-1 w-80 max-w-full flex flex-col gap-3">
<span id="tcs20-lbl" class="text-[13px] font-semibold text-tcs20-mut">Deployment region</span>
<div data-select class="group relative">
<button id="tcs20-trigger" data-trigger type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="tcs20-list" aria-labelledby="tcs20-lbl tcs20-trigger" class="relative w-full flex items-center gap-2.75 min-h-13 px-4 rounded-[15px] border-0 bg-[oklch(0.2_0.03_300/0.45)] backdrop-blur-[18px] text-tcs20-ink cursor-pointer shadow-[inset_0_1px_0_oklch(1_0_0/0.14),0_10px_30px_-12px_oklch(0.05_0_0/0.6)] transition-[box-shadow] duration-200 focus-visible:outline-none focus-visible:shadow-[inset_0_1px_0_oklch(1_0_0/0.2),0_0_0_4px_color-mix(in_oklch,var(--color-tcs20-acc)_30%,transparent)] before:content-[''] before:absolute before:inset-0 before:rounded-[15px] before:p-px before:pointer-events-none before:bg-[linear-gradient(140deg,oklch(1_0_0/0.5),oklch(1_0_0/0.05)_40%,transparent)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask-composite:exclude]">
<span class="w-2.25 h-2.25 flex-none rounded-full bg-tcs20-live shadow-[0_0_10px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>
<span data-name class="flex-1 text-left text-[15px]">US East (N. Virginia)</span>
<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.5 h-4.5 fill-none stroke-tcs20-mut [stroke-width:2.2] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-200 group-data-open:rotate-180 motion-reduce:transition-none"><path d="M6 9l6 6 6-6"/></svg>
</button>
<ul id="tcs20-list" role="listbox" aria-labelledby="tcs20-lbl" tabindex="-1" class="absolute top-[calc(100%+9px)] inset-x-0 z-20 list-none p-1.5 rounded-[15px] border border-[oklch(1_0_0/0.12)] bg-[oklch(0.18_0.03_300/0.62)] backdrop-blur-[18px] shadow-[inset_0_1px_0_oklch(1_0_0/0.1),0_28px_56px_-16px_oklch(0.05_0_0/0.7)] origin-top invisible opacity-0 -translate-y-2.5 scale-98 [transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_.28s] group-data-open:visible group-data-open:opacity-100 group-data-open:translate-y-0 group-data-open:scale-100 group-data-open:[transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_0s] motion-reduce:transition-none">
<li id="tcs20-o0" role="option" aria-selected="true" data-v="US East (N. Virginia)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US East (N. Virginia)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o1" role="option" aria-selected="false" data-v="US West (Oregon)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US West (Oregon)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o2" role="option" aria-selected="false" data-v="EU (Frankfurt)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>EU (Frankfurt)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o3" role="option" aria-selected="false" data-v="Asia Pacific (Tokyo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>Asia Pacific (Tokyo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o4" role="option" aria-selected="false" data-v="South America (São Paulo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>South America (São Paulo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
</ul>
</div>
<p data-live aria-live="polite" class="min-h-4 font-tcs20-mono text-[11px] text-tcs20-mut"></p>
</div>
</section><section class="tcs-20 relative w-full min-h-svh grid place-items-center overflow-hidden bg-tcs20-bg font-tcs20-sans text-tcs20-ink px-5 py-15" aria-label="Glassmorphism dark UI dropdown demo">
<div aria-hidden="true" class="absolute -inset-[20%] z-0 blur-[20px] animate-tcs20-drift motion-reduce:animate-none bg-[radial-gradient(40%_40%_at_25%_30%,oklch(0.6_0.24_303/0.55),transparent_70%),radial-gradient(45%_45%_at_75%_65%,oklch(0.75_0.14_230/0.5),transparent_70%),radial-gradient(35%_35%_at_60%_20%,oklch(0.65_0.22_352/0.4),transparent_70%)]"></div>
<div class="relative z-1 w-80 max-w-full flex flex-col gap-3">
<span id="tcs20-lbl" class="text-[13px] font-semibold text-tcs20-mut">Deployment region</span>
<div data-select class="group relative">
<button id="tcs20-trigger" data-trigger type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="tcs20-list" aria-labelledby="tcs20-lbl tcs20-trigger" class="relative w-full flex items-center gap-2.75 min-h-13 px-4 rounded-[15px] border-0 bg-[oklch(0.2_0.03_300/0.45)] backdrop-blur-[18px] text-tcs20-ink cursor-pointer shadow-[inset_0_1px_0_oklch(1_0_0/0.14),0_10px_30px_-12px_oklch(0.05_0_0/0.6)] transition-[box-shadow] duration-200 focus-visible:outline-none focus-visible:shadow-[inset_0_1px_0_oklch(1_0_0/0.2),0_0_0_4px_color-mix(in_oklch,var(--color-tcs20-acc)_30%,transparent)] before:content-[''] before:absolute before:inset-0 before:rounded-[15px] before:p-px before:pointer-events-none before:bg-[linear-gradient(140deg,oklch(1_0_0/0.5),oklch(1_0_0/0.05)_40%,transparent)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask-composite:exclude]">
<span class="w-2.25 h-2.25 flex-none rounded-full bg-tcs20-live shadow-[0_0_10px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>
<span data-name class="flex-1 text-left text-[15px]">US East (N. Virginia)</span>
<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.5 h-4.5 fill-none stroke-tcs20-mut [stroke-width:2.2] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-200 group-data-open:rotate-180 motion-reduce:transition-none"><path d="M6 9l6 6 6-6"/></svg>
</button>
<ul id="tcs20-list" role="listbox" aria-labelledby="tcs20-lbl" tabindex="-1" class="absolute top-[calc(100%+9px)] inset-x-0 z-20 list-none p-1.5 rounded-[15px] border border-[oklch(1_0_0/0.12)] bg-[oklch(0.18_0.03_300/0.62)] backdrop-blur-[18px] shadow-[inset_0_1px_0_oklch(1_0_0/0.1),0_28px_56px_-16px_oklch(0.05_0_0/0.7)] origin-top invisible opacity-0 -translate-y-2.5 scale-98 [transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_.28s] group-data-open:visible group-data-open:opacity-100 group-data-open:translate-y-0 group-data-open:scale-100 group-data-open:[transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_0s] motion-reduce:transition-none">
<li id="tcs20-o0" role="option" aria-selected="true" data-v="US East (N. Virginia)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US East (N. Virginia)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o1" role="option" aria-selected="false" data-v="US West (Oregon)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US West (Oregon)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o2" role="option" aria-selected="false" data-v="EU (Frankfurt)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>EU (Frankfurt)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o3" role="option" aria-selected="false" data-v="Asia Pacific (Tokyo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>Asia Pacific (Tokyo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o4" role="option" aria-selected="false" data-v="South America (São Paulo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>South America (São Paulo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
</ul>
</div>
<p data-live aria-live="polite" class="min-h-4 font-tcs20-mono text-[11px] text-tcs20-mut"></p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}@import "tailwindcss";
@theme {
--color-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}(function () {
document.querySelectorAll('.tcs-20 [data-select]').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var trig = root.querySelector('[data-trigger]');
var list = root.querySelector('[role="listbox"]');
var opts = Array.prototype.slice.call(root.querySelectorAll('[role="option"]'));
var live = root.closest('.tcs-20').querySelector('[data-live]');
var i = opts.findIndex(function (o) { return o.getAttribute('aria-selected') === 'true'; });
if (i < 0) i = 0;
var typed = '', timer;
function keepInView(el) {
var top = el.offsetTop, bottom = top + el.offsetHeight;
if (top < list.scrollTop) list.scrollTop = top;
else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
}
function mark(n) {
i = n;
opts.forEach(function (o, k) { o.toggleAttribute('data-active', k === n); });
if (opts[n].id) trig.setAttribute('aria-activedescendant', opts[n].id);
keepInView(opts[n]);
}
function set(open) {
root.toggleAttribute('data-open', open);
trig.setAttribute('aria-expanded', String(open));
if (open) mark(i);
else {
opts.forEach(function (o) { o.removeAttribute('data-active'); });
trig.removeAttribute('aria-activedescendant');
}
}
function pick(n) {
opts.forEach(function (o) { o.setAttribute('aria-selected', 'false'); });
opts[n].setAttribute('aria-selected', 'true');
i = n;
root.dispatchEvent(new CustomEvent('tcs:pick', { bubbles: true, detail: { opt: opts[n], index: n } }));
if (live) live.textContent = (opts[n].dataset.v || opts[n].textContent).trim() + ' selected';
set(false);
trig.focus();
}
trig.addEventListener('click', function () { set(!root.hasAttribute('data-open')); });
opts.forEach(function (o, k) {
o.addEventListener('click', function () { pick(k); });
o.addEventListener('mousemove', function () { if (i !== k && root.hasAttribute('data-open')) mark(k); });
});
root.addEventListener('keydown', function (e) {
var open = root.hasAttribute('data-open');
if (!open) {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); set(true); }
return;
}
if (e.key === 'ArrowDown') { e.preventDefault(); mark((i + 1) % opts.length); }
else if (e.key === 'ArrowUp') { e.preventDefault(); mark((i - 1 + opts.length) % opts.length); }
else if (e.key === 'Home') { e.preventDefault(); mark(0); }
else if (e.key === 'End') { e.preventDefault(); mark(opts.length - 1); }
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(i); }
else if (e.key === 'Escape' || e.key === 'Tab') { set(false); if (e.key === 'Escape') { e.preventDefault(); trig.focus(); } }
else if (e.key.length === 1 && e.key.trim()) {
clearTimeout(timer);
typed += e.key.toLowerCase();
timer = setTimeout(function () { typed = ''; }, 600);
var hit = opts.findIndex(function (o) { return (o.dataset.v || o.textContent).trim().toLowerCase().indexOf(typed) === 0; });
if (hit >= 0) mark(hit);
}
});
document.addEventListener('pointerdown', function (e) { if (!root.contains(e.target)) set(false); });
});
})();
(function () {
document.querySelectorAll('.tcs-20').forEach(function (root) {
root.addEventListener('tcs:pick', function (e) {
root.querySelector('[data-name]').textContent = e.detail.opt.dataset.v;
});
});
})();(function () {
document.querySelectorAll('.tcs-20 [data-select]').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var trig = root.querySelector('[data-trigger]');
var list = root.querySelector('[role="listbox"]');
var opts = Array.prototype.slice.call(root.querySelectorAll('[role="option"]'));
var live = root.closest('.tcs-20').querySelector('[data-live]');
var i = opts.findIndex(function (o) { return o.getAttribute('aria-selected') === 'true'; });
if (i < 0) i = 0;
var typed = '', timer;
function keepInView(el) {
var top = el.offsetTop, bottom = top + el.offsetHeight;
if (top < list.scrollTop) list.scrollTop = top;
else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
}
function mark(n) {
i = n;
opts.forEach(function (o, k) { o.toggleAttribute('data-active', k === n); });
if (opts[n].id) trig.setAttribute('aria-activedescendant', opts[n].id);
keepInView(opts[n]);
}
function set(open) {
root.toggleAttribute('data-open', open);
trig.setAttribute('aria-expanded', String(open));
if (open) mark(i);
else {
opts.forEach(function (o) { o.removeAttribute('data-active'); });
trig.removeAttribute('aria-activedescendant');
}
}
function pick(n) {
opts.forEach(function (o) { o.setAttribute('aria-selected', 'false'); });
opts[n].setAttribute('aria-selected', 'true');
i = n;
root.dispatchEvent(new CustomEvent('tcs:pick', { bubbles: true, detail: { opt: opts[n], index: n } }));
if (live) live.textContent = (opts[n].dataset.v || opts[n].textContent).trim() + ' selected';
set(false);
trig.focus();
}
trig.addEventListener('click', function () { set(!root.hasAttribute('data-open')); });
opts.forEach(function (o, k) {
o.addEventListener('click', function () { pick(k); });
o.addEventListener('mousemove', function () { if (i !== k && root.hasAttribute('data-open')) mark(k); });
});
root.addEventListener('keydown', function (e) {
var open = root.hasAttribute('data-open');
if (!open) {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); set(true); }
return;
}
if (e.key === 'ArrowDown') { e.preventDefault(); mark((i + 1) % opts.length); }
else if (e.key === 'ArrowUp') { e.preventDefault(); mark((i - 1 + opts.length) % opts.length); }
else if (e.key === 'Home') { e.preventDefault(); mark(0); }
else if (e.key === 'End') { e.preventDefault(); mark(opts.length - 1); }
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(i); }
else if (e.key === 'Escape' || e.key === 'Tab') { set(false); if (e.key === 'Escape') { e.preventDefault(); trig.focus(); } }
else if (e.key.length === 1 && e.key.trim()) {
clearTimeout(timer);
typed += e.key.toLowerCase();
timer = setTimeout(function () { typed = ''; }, 600);
var hit = opts.findIndex(function (o) { return (o.dataset.v || o.textContent).trim().toLowerCase().indexOf(typed) === 0; });
if (hit >= 0) mark(hit);
}
});
document.addEventListener('pointerdown', function (e) { if (!root.contains(e.target)) set(false); });
});
})();
(function () {
document.querySelectorAll('.tcs-20').forEach(function (root) {
root.addEventListener('tcs:pick', function (e) {
root.querySelector('[data-name]').textContent = e.detail.opt.dataset.v;
});
});
})();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-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}@import "tailwindcss";
@theme {
--color-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}<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=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&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=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
@keyframesstay in the stylesheet — there is no utility form — but the animation shorthand becomes a@themeentry and therefore a realanimate-*utility. - The aurora's three
radial-gradients collapse into one background utility on one element. rgba()alpha becomesoklch(... / a)inside the colour, so no separate opacity utility fights the blur.- Inner highlight and drop shadow compose in a single arbitrary
shadow-[...]value. - Port addition: the live dot now tracks
aria-selectedthrough a named group, and the region choice is announced politely.
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 Glassmorphism Dark UI Dropdown Select
Source: https://codefronts.com/components/tailwind-css-custom-selects/tailwind-glassmorphism-dark-ui-dropdown-select/
The showpiece: a frosted select floating over an aurora that drifts behind it. --animate-tcs20-drift in @theme mints animate-tcs20-drift, the three-blob backdrop is a single arbitrary bg-[radial-gradient(...),...], and the only CSS left is the keyframes and Safari's mask shim.
## HTML
```html
<section class="tcs-20 relative w-full min-h-svh grid place-items-center overflow-hidden bg-tcs20-bg font-tcs20-sans text-tcs20-ink px-5 py-15" aria-label="Glassmorphism dark UI dropdown demo">
<div aria-hidden="true" class="absolute -inset-[20%] z-0 blur-[20px] animate-tcs20-drift motion-reduce:animate-none bg-[radial-gradient(40%_40%_at_25%_30%,oklch(0.6_0.24_303/0.55),transparent_70%),radial-gradient(45%_45%_at_75%_65%,oklch(0.75_0.14_230/0.5),transparent_70%),radial-gradient(35%_35%_at_60%_20%,oklch(0.65_0.22_352/0.4),transparent_70%)]"></div>
<div class="relative z-1 w-80 max-w-full flex flex-col gap-3">
<span id="tcs20-lbl" class="text-[13px] font-semibold text-tcs20-mut">Deployment region</span>
<div data-select class="group relative">
<button id="tcs20-trigger" data-trigger type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="tcs20-list" aria-labelledby="tcs20-lbl tcs20-trigger" class="relative w-full flex items-center gap-2.75 min-h-13 px-4 rounded-[15px] border-0 bg-[oklch(0.2_0.03_300/0.45)] backdrop-blur-[18px] text-tcs20-ink cursor-pointer shadow-[inset_0_1px_0_oklch(1_0_0/0.14),0_10px_30px_-12px_oklch(0.05_0_0/0.6)] transition-[box-shadow] duration-200 focus-visible:outline-none focus-visible:shadow-[inset_0_1px_0_oklch(1_0_0/0.2),0_0_0_4px_color-mix(in_oklch,var(--color-tcs20-acc)_30%,transparent)] before:content-[''] before:absolute before:inset-0 before:rounded-[15px] before:p-px before:pointer-events-none before:bg-[linear-gradient(140deg,oklch(1_0_0/0.5),oklch(1_0_0/0.05)_40%,transparent)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask-composite:exclude]">
<span class="w-2.25 h-2.25 flex-none rounded-full bg-tcs20-live shadow-[0_0_10px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>
<span data-name class="flex-1 text-left text-[15px]">US East (N. Virginia)</span>
<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.5 h-4.5 fill-none stroke-tcs20-mut [stroke-width:2.2] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-200 group-data-open:rotate-180 motion-reduce:transition-none"><path d="M6 9l6 6 6-6"/></svg>
</button>
<ul id="tcs20-list" role="listbox" aria-labelledby="tcs20-lbl" tabindex="-1" class="absolute top-[calc(100%+9px)] inset-x-0 z-20 list-none p-1.5 rounded-[15px] border border-[oklch(1_0_0/0.12)] bg-[oklch(0.18_0.03_300/0.62)] backdrop-blur-[18px] shadow-[inset_0_1px_0_oklch(1_0_0/0.1),0_28px_56px_-16px_oklch(0.05_0_0/0.7)] origin-top invisible opacity-0 -translate-y-2.5 scale-98 [transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_.28s] group-data-open:visible group-data-open:opacity-100 group-data-open:translate-y-0 group-data-open:scale-100 group-data-open:[transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_0s] motion-reduce:transition-none">
<li id="tcs20-o0" role="option" aria-selected="true" data-v="US East (N. Virginia)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US East (N. Virginia)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o1" role="option" aria-selected="false" data-v="US West (Oregon)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US West (Oregon)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o2" role="option" aria-selected="false" data-v="EU (Frankfurt)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>EU (Frankfurt)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o3" role="option" aria-selected="false" data-v="Asia Pacific (Tokyo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>Asia Pacific (Tokyo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o4" role="option" aria-selected="false" data-v="South America (São Paulo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>South America (São Paulo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
</ul>
</div>
<p data-live aria-live="polite" class="min-h-4 font-tcs20-mono text-[11px] text-tcs20-mut"></p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tcs-20 [data-select]').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var trig = root.querySelector('[data-trigger]');
var list = root.querySelector('[role="listbox"]');
var opts = Array.prototype.slice.call(root.querySelectorAll('[role="option"]'));
var live = root.closest('.tcs-20').querySelector('[data-live]');
var i = opts.findIndex(function (o) { return o.getAttribute('aria-selected') === 'true'; });
if (i < 0) i = 0;
var typed = '', timer;
function keepInView(el) {
var top = el.offsetTop, bottom = top + el.offsetHeight;
if (top < list.scrollTop) list.scrollTop = top;
else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
}
function mark(n) {
i = n;
opts.forEach(function (o, k) { o.toggleAttribute('data-active', k === n); });
if (opts[n].id) trig.setAttribute('aria-activedescendant', opts[n].id);
keepInView(opts[n]);
}
function set(open) {
root.toggleAttribute('data-open', open);
trig.setAttribute('aria-expanded', String(open));
if (open) mark(i);
else {
opts.forEach(function (o) { o.removeAttribute('data-active'); });
trig.removeAttribute('aria-activedescendant');
}
}
function pick(n) {
opts.forEach(function (o) { o.setAttribute('aria-selected', 'false'); });
opts[n].setAttribute('aria-selected', 'true');
i = n;
root.dispatchEvent(new CustomEvent('tcs:pick', { bubbles: true, detail: { opt: opts[n], index: n } }));
if (live) live.textContent = (opts[n].dataset.v || opts[n].textContent).trim() + ' selected';
set(false);
trig.focus();
}
trig.addEventListener('click', function () { set(!root.hasAttribute('data-open')); });
opts.forEach(function (o, k) {
o.addEventListener('click', function () { pick(k); });
o.addEventListener('mousemove', function () { if (i !== k && root.hasAttribute('data-open')) mark(k); });
});
root.addEventListener('keydown', function (e) {
var open = root.hasAttribute('data-open');
if (!open) {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); set(true); }
return;
}
if (e.key === 'ArrowDown') { e.preventDefault(); mark((i + 1) % opts.length); }
else if (e.key === 'ArrowUp') { e.preventDefault(); mark((i - 1 + opts.length) % opts.length); }
else if (e.key === 'Home') { e.preventDefault(); mark(0); }
else if (e.key === 'End') { e.preventDefault(); mark(opts.length - 1); }
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(i); }
else if (e.key === 'Escape' || e.key === 'Tab') { set(false); if (e.key === 'Escape') { e.preventDefault(); trig.focus(); } }
else if (e.key.length === 1 && e.key.trim()) {
clearTimeout(timer);
typed += e.key.toLowerCase();
timer = setTimeout(function () { typed = ''; }, 600);
var hit = opts.findIndex(function (o) { return (o.dataset.v || o.textContent).trim().toLowerCase().indexOf(typed) === 0; });
if (hit >= 0) mark(hit);
}
});
document.addEventListener('pointerdown', function (e) { if (!root.contains(e.target)) set(false); });
});
})();
(function () {
document.querySelectorAll('.tcs-20').forEach(function (root) {
root.addEventListener('tcs:pick', function (e) {
root.querySelector('[data-name]').textContent = e.detail.opt.dataset.v;
});
});
})();
```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 Glassmorphism Dark UI Dropdown Select
Source: https://codefronts.com/components/tailwind-css-custom-selects/tailwind-glassmorphism-dark-ui-dropdown-select/
The showpiece: a frosted select floating over an aurora that drifts behind it. --animate-tcs20-drift in @theme mints animate-tcs20-drift, the three-blob backdrop is a single arbitrary bg-[radial-gradient(...),...], and the only CSS left is the keyframes and Safari's mask shim.
## HTML
```html
<section class="tcs-20 relative w-full min-h-svh grid place-items-center overflow-hidden bg-tcs20-bg font-tcs20-sans text-tcs20-ink px-5 py-15" aria-label="Glassmorphism dark UI dropdown demo">
<div aria-hidden="true" class="absolute -inset-[20%] z-0 blur-[20px] animate-tcs20-drift motion-reduce:animate-none bg-[radial-gradient(40%_40%_at_25%_30%,oklch(0.6_0.24_303/0.55),transparent_70%),radial-gradient(45%_45%_at_75%_65%,oklch(0.75_0.14_230/0.5),transparent_70%),radial-gradient(35%_35%_at_60%_20%,oklch(0.65_0.22_352/0.4),transparent_70%)]"></div>
<div class="relative z-1 w-80 max-w-full flex flex-col gap-3">
<span id="tcs20-lbl" class="text-[13px] font-semibold text-tcs20-mut">Deployment region</span>
<div data-select class="group relative">
<button id="tcs20-trigger" data-trigger type="button" aria-haspopup="listbox" aria-expanded="false" aria-controls="tcs20-list" aria-labelledby="tcs20-lbl tcs20-trigger" class="relative w-full flex items-center gap-2.75 min-h-13 px-4 rounded-[15px] border-0 bg-[oklch(0.2_0.03_300/0.45)] backdrop-blur-[18px] text-tcs20-ink cursor-pointer shadow-[inset_0_1px_0_oklch(1_0_0/0.14),0_10px_30px_-12px_oklch(0.05_0_0/0.6)] transition-[box-shadow] duration-200 focus-visible:outline-none focus-visible:shadow-[inset_0_1px_0_oklch(1_0_0/0.2),0_0_0_4px_color-mix(in_oklch,var(--color-tcs20-acc)_30%,transparent)] before:content-[''] before:absolute before:inset-0 before:rounded-[15px] before:p-px before:pointer-events-none before:bg-[linear-gradient(140deg,oklch(1_0_0/0.5),oklch(1_0_0/0.05)_40%,transparent)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask-composite:exclude]">
<span class="w-2.25 h-2.25 flex-none rounded-full bg-tcs20-live shadow-[0_0_10px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>
<span data-name class="flex-1 text-left text-[15px]">US East (N. Virginia)</span>
<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.5 h-4.5 fill-none stroke-tcs20-mut [stroke-width:2.2] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-200 group-data-open:rotate-180 motion-reduce:transition-none"><path d="M6 9l6 6 6-6"/></svg>
</button>
<ul id="tcs20-list" role="listbox" aria-labelledby="tcs20-lbl" tabindex="-1" class="absolute top-[calc(100%+9px)] inset-x-0 z-20 list-none p-1.5 rounded-[15px] border border-[oklch(1_0_0/0.12)] bg-[oklch(0.18_0.03_300/0.62)] backdrop-blur-[18px] shadow-[inset_0_1px_0_oklch(1_0_0/0.1),0_28px_56px_-16px_oklch(0.05_0_0/0.7)] origin-top invisible opacity-0 -translate-y-2.5 scale-98 [transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_.28s] group-data-open:visible group-data-open:opacity-100 group-data-open:translate-y-0 group-data-open:scale-100 group-data-open:[transition:opacity_.22s,translate_.28s_var(--ease-tcs20),scale_.28s_var(--ease-tcs20),visibility_0s_0s] motion-reduce:transition-none">
<li id="tcs20-o0" role="option" aria-selected="true" data-v="US East (N. Virginia)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US East (N. Virginia)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o1" role="option" aria-selected="false" data-v="US West (Oregon)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>US West (Oregon)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o2" role="option" aria-selected="false" data-v="EU (Frankfurt)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>EU (Frankfurt)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o3" role="option" aria-selected="false" data-v="Asia Pacific (Tokyo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>Asia Pacific (Tokyo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
<li id="tcs20-o4" role="option" aria-selected="false" data-v="South America (São Paulo)" class="group/opt flex items-center gap-2.75 min-h-11 px-3 rounded-[10px] text-[14.5px] text-tcs20-ink2 cursor-pointer transition-colors duration-150 data-active:bg-[oklch(1_0_0/0.08)] aria-selected:bg-[oklch(1_0_0/0.06)]"><span class="w-1.75 h-1.75 flex-none rounded-full bg-[oklch(1_0_0/0.35)] transition-[background-color,box-shadow] duration-150 group-aria-selected/opt:bg-tcs20-live group-aria-selected/opt:shadow-[0_0_8px_-1px_var(--color-tcs20-live)]" aria-hidden="true"></span>South America (São Paulo)<svg viewBox="0 0 24 24" aria-hidden="true" class="w-4.25 h-4.25 ml-auto fill-none stroke-tcs20-acc [stroke-width:2.4] [stroke-linecap:round] [stroke-linejoin:round] opacity-0 transition-opacity duration-150 group-aria-selected/opt:opacity-100"><path d="M5 12l5 5L20 7"/></svg></li>
</ul>
</div>
<p data-live aria-live="polite" class="min-h-4 font-tcs20-mono text-[11px] text-tcs20-mut"></p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcs20-bg: #0a0714;
--color-tcs20-bg: oklch(0.14 0.03 300);
--color-tcs20-ink: #f2ecfb;
--color-tcs20-ink: oklch(0.95 0.02 300);
--color-tcs20-ink2: #e9e1f7;
--color-tcs20-ink2: oklch(0.91 0.03 300);
--color-tcs20-mut: #b9abd0;
--color-tcs20-mut: oklch(0.75 0.05 300);
--color-tcs20-acc: #c084fc;
--color-tcs20-acc: oklch(0.75 0.18 303);
--color-tcs20-live: #4ade80;
--color-tcs20-live: oklch(0.82 0.19 148);
--font-tcs20-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcs20-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tcs20: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tcs20-drift: tcs20-drift 16s ease-in-out infinite alternate;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build has compiled. */
.tcs-20 {
width: 100%;
min-height: 100svh;
}
/* @keyframes have no utility form, and the -webkit- mask pair for the glass edge has no
variant. Everything else in this demo — blur, translucency, inner highlight — is a utility. */
@keyframes tcs20-drift {
0% {
transform: translate(-4%, -3%) scale(1);
}
50% {
transform: translate(5%, 4%) scale(1.12);
}
100% {
transform: translate(-3%, 5%) scale(1.05);
}
}
.tcs-20 [data-trigger]::before {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tcs-20 [data-select]').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var trig = root.querySelector('[data-trigger]');
var list = root.querySelector('[role="listbox"]');
var opts = Array.prototype.slice.call(root.querySelectorAll('[role="option"]'));
var live = root.closest('.tcs-20').querySelector('[data-live]');
var i = opts.findIndex(function (o) { return o.getAttribute('aria-selected') === 'true'; });
if (i < 0) i = 0;
var typed = '', timer;
function keepInView(el) {
var top = el.offsetTop, bottom = top + el.offsetHeight;
if (top < list.scrollTop) list.scrollTop = top;
else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
}
function mark(n) {
i = n;
opts.forEach(function (o, k) { o.toggleAttribute('data-active', k === n); });
if (opts[n].id) trig.setAttribute('aria-activedescendant', opts[n].id);
keepInView(opts[n]);
}
function set(open) {
root.toggleAttribute('data-open', open);
trig.setAttribute('aria-expanded', String(open));
if (open) mark(i);
else {
opts.forEach(function (o) { o.removeAttribute('data-active'); });
trig.removeAttribute('aria-activedescendant');
}
}
function pick(n) {
opts.forEach(function (o) { o.setAttribute('aria-selected', 'false'); });
opts[n].setAttribute('aria-selected', 'true');
i = n;
root.dispatchEvent(new CustomEvent('tcs:pick', { bubbles: true, detail: { opt: opts[n], index: n } }));
if (live) live.textContent = (opts[n].dataset.v || opts[n].textContent).trim() + ' selected';
set(false);
trig.focus();
}
trig.addEventListener('click', function () { set(!root.hasAttribute('data-open')); });
opts.forEach(function (o, k) {
o.addEventListener('click', function () { pick(k); });
o.addEventListener('mousemove', function () { if (i !== k && root.hasAttribute('data-open')) mark(k); });
});
root.addEventListener('keydown', function (e) {
var open = root.hasAttribute('data-open');
if (!open) {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); set(true); }
return;
}
if (e.key === 'ArrowDown') { e.preventDefault(); mark((i + 1) % opts.length); }
else if (e.key === 'ArrowUp') { e.preventDefault(); mark((i - 1 + opts.length) % opts.length); }
else if (e.key === 'Home') { e.preventDefault(); mark(0); }
else if (e.key === 'End') { e.preventDefault(); mark(opts.length - 1); }
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(i); }
else if (e.key === 'Escape' || e.key === 'Tab') { set(false); if (e.key === 'Escape') { e.preventDefault(); trig.focus(); } }
else if (e.key.length === 1 && e.key.trim()) {
clearTimeout(timer);
typed += e.key.toLowerCase();
timer = setTimeout(function () { typed = ''; }, 600);
var hit = opts.findIndex(function (o) { return (o.dataset.v || o.textContent).trim().toLowerCase().indexOf(typed) === 0; });
if (hit >= 0) mark(hit);
}
});
document.addEventListener('pointerdown', function (e) { if (!root.contains(e.target)) set(false); });
});
})();
(function () {
document.querySelectorAll('.tcs-20').forEach(function (root) {
root.addEventListener('tcs:pick', function (e) {
root.querySelector('[data-name]').textContent = e.detail.opt.dataset.v;
});
});
})();
```How this works
Glass is three layers. A translucent background, a backdrop blur, and an inner top highlight — all utilities on the same element. The hairline gradient border is a masked before:, which is the same trick demo 02 uses and the same Safari shim.
The aurora is one element. Three radial gradients in a single background utility, inflated past the section with -inset-[20%] so the blur never shows an edge, and animated by a theme-minted animation utility.
Contrast first, frost second. Backdrop blur is not guaranteed — it can be disabled, unsupported or dropped under GPU pressure. Both surfaces carry enough alpha to stay legible with the filter off, which is the part most glassmorphism gets wrong.
Make it yours
- Frost: the
[18px]inbackdrop-blur-[18px]and the alpha in the two surface colours. - Aurora palette: the three
oklch()colours in the background utility. - Drift speed: the
16sin--animate-tcs20-drift. - Edge brightness: the first stop of the
before:bg-[linear-gradient(...)].
Gotchas — read before shipping
- Text contrast must survive without
backdrop-filter. Keep the surface alpha high enough to read on its own. backdrop-filteris expensive and stacking two blurred layers doubles the cost — this demo blurs the trigger and the panel only.- Safari still needs the
-webkit-mask-composite: xorshim for the gradient edge. - Animated backdrops must respect
prefers-reduced-motion; drift is exactly the kind of ambient motion that triggers vestibular discomfort. - A blurred ancestor creates a containing block — do not put
fixedchildren inside the panel.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
backdrop-filter needs Firefox 103+; without it the translucent surfaces still read correctly.