25 / 25
All 25 in 25 Tailwind CSS Button Groups
- 01Tailwind Split Button with Anchor-Positioned Popover Dropdown (No JS)
- 02Tailwind Segmented Control with Motion-Path Comet Indicator
- 03Tailwind Toggle Button Group with View Transitions Morphing Highlight
- 04Tailwind Pricing Toggle — Monthly / Yearly Billing Switch (No JS)
- 05Tailwind Segmented Control with Sliding Thumb (iOS Style)
- 06Tailwind Split Button Dropdown with Native Details Menu (Save Options)
- 07Tailwind Icon Button Group Toolbar with Tooltips (Rich Text Editor)
- 08Tailwind Pill Filter Button Group with Sliding Indicator (Anchor Positioning)
- 09Tailwind Toggle Button Group for Text Alignment (Radio Inputs, Live Preview)
- 10Tailwind Web3 Connect Wallet Button Grid (Wallet Sign-In Modal)
- 11Tailwind AI Model Selector Button Group (LLM Model Picker with Badges)
- 12Tailwind Liquid Metal Segmented Control (Animated Chrome Thumb)
- 13Tailwind Tab Button Group with Subgrid Underline Indicator
- 14Tailwind Multi-Select Chip Buttons (Tag Filter Checkboxes with Count)
- 15Tailwind Animated Split Button Deploy Menu (Staggered Dropdown Reveal)
- 16Tailwind Radio Button Group with Conic Fill Animation (Export Format Picker)
- 17Tailwind Retro LED Pixel Button Group (Dot-Matrix Display Buttons)
- 18Tailwind Hold-to-Confirm Button Group with Gradient Progress (Press and Hold)
- 19Tailwind Holographic Icon Segmented Control (Grid / List View Switcher)
- 20Tailwind 3D Vertical Button Group with Hover Depth (Perspective Stack)
- 21Tailwind Floating Action Button with Radial Speed Dial Menu (No JS)
- 22Tailwind Glitch Effect Button Group (Cyberpunk RGB-Split Audio Filter)
- 23Tailwind Animated Gradient Border Pill Buttons (Rotating Conic Ring)
- 24Tailwind Button Group with Ripple Click Effect (Radar Shockwave Feedback)
- 25Tailwind Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Tailwind Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Date-range preset buttons lit by a warm spotlight that follows the cursor across the whole group — one glow spanning every button, because each reads the same pointer coordinates from --mx/--my. Selection is pure radios; the script only writes two variables. Below the presets, the resolved range (Oct 2 – Oct 8, 2026) updates as you choose, so users see exactly which dates they're getting.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tbg-25 w-full min-h-svh grid place-items-center bg-tbg25-bg bg-[radial-gradient(130%_120%_at_50%_0%,var(--color-tbg25-glow),transparent_70%)] px-5 py-14 font-tbg25-sans text-tbg25-ink" aria-label="Cursor-following spotlight button group demo">
<div class="group/w grid w-full max-w-[38rem] gap-4">
<p id="tbg25-label" class="ml-0.5 text-xs font-extrabold uppercase tracking-[0.16em] text-tbg25-acc">Date range</p>
<div data-tbg25-group role="radiogroup" aria-labelledby="tbg25-label" class="group/g flex flex-wrap gap-3">
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-today" class="sr-only">Today</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-7d" class="sr-only" checked>Last 7 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-30d" class="sr-only">Last 30 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-q" class="sr-only">Quarter</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-y" class="sr-only">Year to date</label>
</div>
<p class="ml-0.5 flex items-center gap-2 text-sm text-tbg25-mut"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg><span class="font-semibold text-tbg25-ink"><span class="hidden group-has-[#tbg25-today:checked]/w:inline">Oct 8, 2026</span><span class="hidden group-has-[#tbg25-7d:checked]/w:inline">Oct 2 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-30d:checked]/w:inline">Sep 9 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-q:checked]/w:inline">Oct 1 – Oct 8, 2026 (Q4)</span><span class="hidden group-has-[#tbg25-y:checked]/w:inline">Jan 1 – Oct 8, 2026</span></span></p>
</div>
</section><section class="tbg-25 w-full min-h-svh grid place-items-center bg-tbg25-bg bg-[radial-gradient(130%_120%_at_50%_0%,var(--color-tbg25-glow),transparent_70%)] px-5 py-14 font-tbg25-sans text-tbg25-ink" aria-label="Cursor-following spotlight button group demo">
<div class="group/w grid w-full max-w-[38rem] gap-4">
<p id="tbg25-label" class="ml-0.5 text-xs font-extrabold uppercase tracking-[0.16em] text-tbg25-acc">Date range</p>
<div data-tbg25-group role="radiogroup" aria-labelledby="tbg25-label" class="group/g flex flex-wrap gap-3">
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-today" class="sr-only">Today</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-7d" class="sr-only" checked>Last 7 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-30d" class="sr-only">Last 30 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-q" class="sr-only">Quarter</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-y" class="sr-only">Year to date</label>
</div>
<p class="ml-0.5 flex items-center gap-2 text-sm text-tbg25-mut"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg><span class="font-semibold text-tbg25-ink"><span class="hidden group-has-[#tbg25-today:checked]/w:inline">Oct 8, 2026</span><span class="hidden group-has-[#tbg25-7d:checked]/w:inline">Oct 2 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-30d:checked]/w:inline">Sep 9 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-q:checked]/w:inline">Oct 1 – Oct 8, 2026 (Q4)</span><span class="hidden group-has-[#tbg25-y:checked]/w:inline">Jan 1 – Oct 8, 2026</span></span></p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}(() => {
document.querySelectorAll('[data-tbg25-group]').forEach((group) => {
if (group.dataset.wired) return;
group.dataset.wired = '1';
const btns = [...group.querySelectorAll('label')];
group.addEventListener('pointermove', (e) => {
btns.forEach((b) => {
const r = b.getBoundingClientRect();
b.style.setProperty('--mx', (e.clientX - r.left) + 'px');
b.style.setProperty('--my', (e.clientY - r.top) + 'px');
});
}, { passive: true });
group.addEventListener('pointerleave', () => {
btns.forEach((b) => { b.style.removeProperty('--mx'); b.style.removeProperty('--my'); });
});
});
})();(() => {
document.querySelectorAll('[data-tbg25-group]').forEach((group) => {
if (group.dataset.wired) return;
group.dataset.wired = '1';
const btns = [...group.querySelectorAll('label')];
group.addEventListener('pointermove', (e) => {
btns.forEach((b) => {
const r = b.getBoundingClientRect();
b.style.setProperty('--mx', (e.clientX - r.left) + 'px');
b.style.setProperty('--my', (e.clientY - r.top) + 'px');
});
}, { passive: true });
group.addEventListener('pointerleave', () => {
btns.forEach((b) => { b.style.removeProperty('--mx'); b.style.removeProperty('--my'); });
});
});
})();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-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}<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=Hanken+Grotesk:wght@400..800&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=Hanken+Grotesk:wght@400..800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
::beforespotlight becamebefore:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),…)]; the group-hover rule becamegroup-hover/g:before:opacity-100. - Default coordinates moved from an inline style on the group into
[--mx:50%] [--my:50%]on each label, so the no-JS glow is centred per button. - UI upgrade: the resolved date range is shown under the presets, keyboard focus lights the focused button, and the spotlight resets when the pointer leaves.
- UI upgrade: “Year” became the clearer “Year to date”; idle labels rose from
#c8b49atooklch(0.81).
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 Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Source: https://codefronts.com/components/tailwind-css-button-groups/tailwind-button-group-cursor-spotlight-glow/
Date-range preset buttons lit by a warm spotlight that follows the cursor across the whole group — one glow spanning every button, because each reads the same pointer coordinates from --mx/--my. Selection is pure radios; the script only writes two variables. Below the presets, the resolved range (Oct 2 – Oct 8, 2026) updates as you choose, so users see exactly which dates they're getting.
## HTML
```html
<section class="tbg-25 w-full min-h-svh grid place-items-center bg-tbg25-bg bg-[radial-gradient(130%_120%_at_50%_0%,var(--color-tbg25-glow),transparent_70%)] px-5 py-14 font-tbg25-sans text-tbg25-ink" aria-label="Cursor-following spotlight button group demo">
<div class="group/w grid w-full max-w-[38rem] gap-4">
<p id="tbg25-label" class="ml-0.5 text-xs font-extrabold uppercase tracking-[0.16em] text-tbg25-acc">Date range</p>
<div data-tbg25-group role="radiogroup" aria-labelledby="tbg25-label" class="group/g flex flex-wrap gap-3">
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-today" class="sr-only">Today</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-7d" class="sr-only" checked>Last 7 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-30d" class="sr-only">Last 30 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-q" class="sr-only">Quarter</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-y" class="sr-only">Year to date</label>
</div>
<p class="ml-0.5 flex items-center gap-2 text-sm text-tbg25-mut"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg><span class="font-semibold text-tbg25-ink"><span class="hidden group-has-[#tbg25-today:checked]/w:inline">Oct 8, 2026</span><span class="hidden group-has-[#tbg25-7d:checked]/w:inline">Oct 2 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-30d:checked]/w:inline">Sep 9 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-q:checked]/w:inline">Oct 1 – Oct 8, 2026 (Q4)</span><span class="hidden group-has-[#tbg25-y:checked]/w:inline">Jan 1 – Oct 8, 2026</span></span></p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('[data-tbg25-group]').forEach((group) => {
if (group.dataset.wired) return;
group.dataset.wired = '1';
const btns = [...group.querySelectorAll('label')];
group.addEventListener('pointermove', (e) => {
btns.forEach((b) => {
const r = b.getBoundingClientRect();
b.style.setProperty('--mx', (e.clientX - r.left) + 'px');
b.style.setProperty('--my', (e.clientY - r.top) + 'px');
});
}, { passive: true });
group.addEventListener('pointerleave', () => {
btns.forEach((b) => { b.style.removeProperty('--mx'); b.style.removeProperty('--my'); });
});
});
})();
```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 Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Source: https://codefronts.com/components/tailwind-css-button-groups/tailwind-button-group-cursor-spotlight-glow/
Date-range preset buttons lit by a warm spotlight that follows the cursor across the whole group — one glow spanning every button, because each reads the same pointer coordinates from --mx/--my. Selection is pure radios; the script only writes two variables. Below the presets, the resolved range (Oct 2 – Oct 8, 2026) updates as you choose, so users see exactly which dates they're getting.
## HTML
```html
<section class="tbg-25 w-full min-h-svh grid place-items-center bg-tbg25-bg bg-[radial-gradient(130%_120%_at_50%_0%,var(--color-tbg25-glow),transparent_70%)] px-5 py-14 font-tbg25-sans text-tbg25-ink" aria-label="Cursor-following spotlight button group demo">
<div class="group/w grid w-full max-w-[38rem] gap-4">
<p id="tbg25-label" class="ml-0.5 text-xs font-extrabold uppercase tracking-[0.16em] text-tbg25-acc">Date range</p>
<div data-tbg25-group role="radiogroup" aria-labelledby="tbg25-label" class="group/g flex flex-wrap gap-3">
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-today" class="sr-only">Today</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-7d" class="sr-only" checked>Last 7 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-30d" class="sr-only">Last 30 days</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-q" class="sr-only">Quarter</label>
<label class="relative isolate flex min-h-12.5 cursor-pointer items-center overflow-hidden rounded-[13px] border-[1.5px] border-tbg25-line bg-tbg25-key px-5.5 text-[15px] font-bold text-tbg25-mut transition-[color,border-color] duration-200 [--mx:50%] [--my:50%] before:absolute before:inset-0 before:-z-1 before:bg-[radial-gradient(220px_circle_at_var(--mx)_var(--my),color-mix(in_oklab,var(--color-tbg25-acc)_55%,transparent),transparent_60%)] before:opacity-0 before:transition-opacity before:duration-300 before:content-[''] group-hover/g:before:opacity-100 has-focus-visible:before:opacity-100 hover:border-tbg25-acc hover:text-tbg25-ink has-checked:border-tbg25-acc has-checked:bg-tbg25-acc has-checked:text-tbg25-on has-checked:shadow-[0_8px_24px_-10px_var(--color-tbg25-acc)] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-tbg25-acc motion-reduce:transition-none"><input type="radio" name="tbg25" id="tbg25-y" class="sr-only">Year to date</label>
</div>
<p class="ml-0.5 flex items-center gap-2 text-sm text-tbg25-mut"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg><span class="font-semibold text-tbg25-ink"><span class="hidden group-has-[#tbg25-today:checked]/w:inline">Oct 8, 2026</span><span class="hidden group-has-[#tbg25-7d:checked]/w:inline">Oct 2 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-30d:checked]/w:inline">Sep 9 – Oct 8, 2026</span><span class="hidden group-has-[#tbg25-q:checked]/w:inline">Oct 1 – Oct 8, 2026 (Q4)</span><span class="hidden group-has-[#tbg25-y:checked]/w:inline">Jan 1 – Oct 8, 2026</span></span></p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbg25-bg: #0b0705;
--color-tbg25-glow: #22160c;
--color-tbg25-key: #1b120b;
--color-tbg25-line: #3a2a1c;
--color-tbg25-ink: #fff2e2;
--color-tbg25-mut: #d2bea3;
--color-tbg25-mut: oklch(0.81 0.04 70);
--color-tbg25-on: #1a0f06;
--color-tbg25-acc: #f5a83a;
--color-tbg25-acc: oklch(0.79 0.15 68);
--font-tbg25-sans: 'Hanken Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */
.tbg-25 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('[data-tbg25-group]').forEach((group) => {
if (group.dataset.wired) return;
group.dataset.wired = '1';
const btns = [...group.querySelectorAll('label')];
group.addEventListener('pointermove', (e) => {
btns.forEach((b) => {
const r = b.getBoundingClientRect();
b.style.setProperty('--mx', (e.clientX - r.left) + 'px');
b.style.setProperty('--my', (e.clientY - r.top) + 'px');
});
}, { passive: true });
group.addEventListener('pointerleave', () => {
btns.forEach((b) => { b.style.removeProperty('--mx'); b.style.removeProperty('--my'); });
});
});
})();
```How this works
One pointer, many spotlights. The listener translates the pointer into each button's own coordinates, so their individual glows line up into one continuous light:
btns.forEach((b) => {
const r = b.getBoundingClientRect();
b.style.setProperty('--mx', (e.clientX - r.left) + 'px');
b.style.setProperty('--my', (e.clientY - r.top) + 'px');
});No JS, no problem. [--mx:50%] [--my:50%] defaults put the glow in the centre, so hover still lights each button without the script.
Make it yours
- Softer light: raise the radius (
220px) and lower the mix percentage. - Different accent:
--color-tbg25-acc; the glow derives from it. - Real dates: compute the range text from
new Date()if presets must stay current.
Gotchas — read before shipping
- The range captions are static text for this demo; in production generate them from today's date.
- Keep
pointermovepassive and avoid heavy work inside it. - Selection never depends on the glow — the checked state is a solid fill with dark text.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
color-mix() Baseline 2023; Tailwind v4 sets the floor. Without JS the glow stays centred.