10 / 17
All 17 in 17 Tailwind CSS Range Sliders
- 01Tailwind CSS Dual-Handle Price Range Slider with Live Product Count
- 02Tailwind CSS Neumorphic Volume Slider with Soft UI Shadows
- 03Tailwind CSS Neon Glow Range Slider for Dark Mode UI
- 04Tailwind CSS Emoji Mood Slider for Feedback & CSAT Surveys
- 05Tailwind CSS Multi-Step Pricing Slider with Snapping Milestones
- 06Tailwind CSS Video Player Seekbar with Hover Scrub & Chapters
- 07Tailwind CSS Vertical Range Sliders — 10-Band Audio Equalizer
- 08Tailwind CSS Circular Dial Range Input — Smart Thermostat UI
- 09Tailwind CSS Gradient Range Slider with Progress Milestones
- 10Tailwind CSS Range Slider with Pop-Up Value Bubble (Loan Calculator)
- 11Tailwind CSS Brutalist Range Slider with Thick Block Track
- 12Tailwind CSS Password Strength Meter with Range Slider Control
- 13Tailwind CSS Centre-Origin Balance Slider for Audio Pan Control
- 14Tailwind CSS Retro 8-Bit Pixel Art Range Slider (Game HP Bar)
- 15Tailwind CSS Isometric 3D Range Slider with Perspective Plane
- 16Tailwind CSS Glassmorphism Range Sliders — RGBA Colour Mixer
- 17Tailwind CSS Liquid Glass Slider with Gooey Morphing Value Bubble
Tailwind CSS Range Slider with Pop-Up Value Bubble (Loan Calculator)
A loan slider whose value bubble springs out of the thumb while you drag, with live monthly payment, total interest and a working term selector. The port deletes four JS event listeners: has-[input:active]: and has-[input:focus-visible]: show the bubble, so pointer and keyboard behave identically.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="trs-10 w-full min-h-svh grid place-items-center bg-trs10-bg font-trs10-sans px-5 py-10" id="trs-10-root" style="--v:.286">
<div class="w-[min(460px,100%)] rounded-3xl bg-trs10-card border border-trs10-edge p-9 shadow-[0_24px_70px_-28px_oklch(0_0_0/0.7)]">
<header class="mb-3">
<h3 class="text-lg font-extrabold text-trs10-ink m-0">Loan calculator</h3>
<p class="text-[13px] text-trs10-mut mt-1 m-0">Drag to adjust your loan amount</p>
</header>
<div class="flex items-baseline gap-1.5 mt-5 mb-11">
<span class="text-2xl font-bold text-trs10-acc2">$</span>
<span class="text-[56px] leading-none font-black tracking-tighter text-trs10-ink tabular-nums" id="trs-10-amount">15,000</span>
<span class="text-sm text-trs10-mut self-end mb-2">loan</span>
</div>
<div class="relative pt-14" id="trs-10-wrap">
<div aria-hidden="true" class="absolute top-0 left-[calc(var(--v)*100%)] -translate-x-1/2 flex flex-col items-center pointer-events-none">
<span class="origin-bottom scale-0 rounded-full bg-trs10-acc px-3.5 py-1.5 font-trs10-mono text-[13px] font-semibold text-white whitespace-nowrap shadow-[0_6px_22px_-4px_var(--color-trs10-acc)] transition-transform duration-250 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-[input:active]/wrap:scale-100 motion-reduce:transition-none" id="trs-10-tip">$15,000</span>
</div>
<div class="group/wrap relative">
<div class="h-2 rounded-full bg-white/8"></div>
<div class="absolute left-0 bottom-0 h-2 rounded-full w-[calc(var(--v)*100%)] bg-gradient-to-r from-trs10-acc to-trs10-acc2 shadow-[0_0_18px_-2px_var(--color-trs10-acc)]"></div>
<input type="range" min="1000" max="50000" step="500" value="15000" id="trs-10-inp" aria-label="Loan amount"
class="absolute -bottom-3.5 left-0 w-full h-9 appearance-none bg-transparent outline-none cursor-pointer z-10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-trs10-acc2 rounded-full">
</div>
</div>
<div class="flex justify-between font-trs10-mono text-[11px] text-trs10-mut mt-3.5">
<span>$1K</span><span>$10K</span><span>$25K</span><span>$50K</span>
</div>
<div class="flex gap-2 mt-6" role="group" aria-label="Loan term" id="trs-10-terms">
<button type="button" data-term="12" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">12 mo</button>
<button type="button" data-term="24" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">24 mo</button>
<button type="button" data-term="36" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">36 mo</button>
<button type="button" data-term="60" aria-pressed="true" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">60 mo</button>
</div>
<div class="grid grid-cols-3 mt-6 pt-5 border-t border-trs10-edge text-center">
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Monthly</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-mo">$298</div>
</div>
<div class="border-x border-trs10-edge">
<div class="text-[11px] tracking-wide text-trs10-mut">Rate APR</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1">6.9%</div>
</div>
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Total interest</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-int">$2,880</div>
</div>
</div>
</div>
</section><section class="trs-10 w-full min-h-svh grid place-items-center bg-trs10-bg font-trs10-sans px-5 py-10" id="trs-10-root" style="--v:.286">
<div class="w-[min(460px,100%)] rounded-3xl bg-trs10-card border border-trs10-edge p-9 shadow-[0_24px_70px_-28px_oklch(0_0_0/0.7)]">
<header class="mb-3">
<h3 class="text-lg font-extrabold text-trs10-ink m-0">Loan calculator</h3>
<p class="text-[13px] text-trs10-mut mt-1 m-0">Drag to adjust your loan amount</p>
</header>
<div class="flex items-baseline gap-1.5 mt-5 mb-11">
<span class="text-2xl font-bold text-trs10-acc2">$</span>
<span class="text-[56px] leading-none font-black tracking-tighter text-trs10-ink tabular-nums" id="trs-10-amount">15,000</span>
<span class="text-sm text-trs10-mut self-end mb-2">loan</span>
</div>
<div class="relative pt-14" id="trs-10-wrap">
<div aria-hidden="true" class="absolute top-0 left-[calc(var(--v)*100%)] -translate-x-1/2 flex flex-col items-center pointer-events-none">
<span class="origin-bottom scale-0 rounded-full bg-trs10-acc px-3.5 py-1.5 font-trs10-mono text-[13px] font-semibold text-white whitespace-nowrap shadow-[0_6px_22px_-4px_var(--color-trs10-acc)] transition-transform duration-250 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-[input:active]/wrap:scale-100 motion-reduce:transition-none" id="trs-10-tip">$15,000</span>
</div>
<div class="group/wrap relative">
<div class="h-2 rounded-full bg-white/8"></div>
<div class="absolute left-0 bottom-0 h-2 rounded-full w-[calc(var(--v)*100%)] bg-gradient-to-r from-trs10-acc to-trs10-acc2 shadow-[0_0_18px_-2px_var(--color-trs10-acc)]"></div>
<input type="range" min="1000" max="50000" step="500" value="15000" id="trs-10-inp" aria-label="Loan amount"
class="absolute -bottom-3.5 left-0 w-full h-9 appearance-none bg-transparent outline-none cursor-pointer z-10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-trs10-acc2 rounded-full">
</div>
</div>
<div class="flex justify-between font-trs10-mono text-[11px] text-trs10-mut mt-3.5">
<span>$1K</span><span>$10K</span><span>$25K</span><span>$50K</span>
</div>
<div class="flex gap-2 mt-6" role="group" aria-label="Loan term" id="trs-10-terms">
<button type="button" data-term="12" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">12 mo</button>
<button type="button" data-term="24" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">24 mo</button>
<button type="button" data-term="36" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">36 mo</button>
<button type="button" data-term="60" aria-pressed="true" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">60 mo</button>
</div>
<div class="grid grid-cols-3 mt-6 pt-5 border-t border-trs10-edge text-center">
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Monthly</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-mo">$298</div>
</div>
<div class="border-x border-trs10-edge">
<div class="text-[11px] tracking-wide text-trs10-mut">Rate APR</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1">6.9%</div>
</div>
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Total interest</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-int">$2,880</div>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}@import "tailwindcss";
@theme {
--color-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}(function(){
var root = document.getElementById('trs-10-root');
var inp = document.getElementById('trs-10-inp');
var tip = document.getElementById('trs-10-tip');
var amount = document.getElementById('trs-10-amount');
var mo = document.getElementById('trs-10-mo');
var interest = document.getElementById('trs-10-int');
var APR = 0.069;
var term = 60;
function money(n){ return '$' + Math.round(n).toLocaleString('en-US'); }
function short(n){ return n >= 1000 ? '$' + Math.round(n / 1000) + 'K' : '$' + n; }
function update(){
var v = +inp.value;
root.style.setProperty('--v', (v - +inp.min) / (+inp.max - +inp.min));
tip.textContent = short(v);
amount.textContent = v.toLocaleString('en-US');
var r = APR / 12;
var m = v * r * Math.pow(1 + r, term) / (Math.pow(1 + r, term) - 1);
mo.textContent = money(m);
interest.textContent = money(m * term - v);
inp.setAttribute('aria-valuetext', money(v) + ', ' + money(m) + ' per month over ' + term + ' months');
}
document.querySelectorAll('#trs-10-terms button').forEach(function(btn){
btn.addEventListener('click', function(){
document.querySelectorAll('#trs-10-terms button').forEach(function(b){ b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
term = +btn.dataset.term;
update();
});
});
inp.addEventListener('input', update);
update();
})();(function(){
var root = document.getElementById('trs-10-root');
var inp = document.getElementById('trs-10-inp');
var tip = document.getElementById('trs-10-tip');
var amount = document.getElementById('trs-10-amount');
var mo = document.getElementById('trs-10-mo');
var interest = document.getElementById('trs-10-int');
var APR = 0.069;
var term = 60;
function money(n){ return '$' + Math.round(n).toLocaleString('en-US'); }
function short(n){ return n >= 1000 ? '$' + Math.round(n / 1000) + 'K' : '$' + n; }
function update(){
var v = +inp.value;
root.style.setProperty('--v', (v - +inp.min) / (+inp.max - +inp.min));
tip.textContent = short(v);
amount.textContent = v.toLocaleString('en-US');
var r = APR / 12;
var m = v * r * Math.pow(1 + r, term) / (Math.pow(1 + r, term) - 1);
mo.textContent = money(m);
interest.textContent = money(m * term - v);
inp.setAttribute('aria-valuetext', money(v) + ', ' + money(m) + ' per month over ' + term + ' months');
}
document.querySelectorAll('#trs-10-terms button').forEach(function(btn){
btn.addEventListener('click', function(){
document.querySelectorAll('#trs-10-terms button').forEach(function(b){ b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
term = +btn.dataset.term;
update();
});
});
inp.addEventListener('input', update);
update();
})();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-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}@import "tailwindcss";
@theme {
--color-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}<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=Outfit:wght@400;500;700;900&family=JetBrains+Mono:wght@600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;700;900&family=JetBrains+Mono:wght@600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Four listeners (
mousedown,touchstart, and two document-level release handlers) became onegroup-has-[input:active]/wrap:scale-100utility — and the bubble now pops for keyboard users too. - Bubble position stopped being a JS style write:
left-[calc(var(--v)*100%)]shares the fill's single source of truth. - UI upgrade: a working 12/24/36/60-month term selector, a total-interest figure, locale-formatted currency, and an
aria-valuetextthat states the payment.
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 CSS Range Slider with Pop-Up Value Bubble (Loan Calculator)
Source: https://codefronts.com/components/tailwind-css-range-sliders/tailwind-range-slider-popup-value-bubble-loan-calculator/
A loan slider whose value bubble springs out of the thumb while you drag, with live monthly payment, total interest and a working term selector. The port deletes four JS event listeners: has-[input:active]: and has-[input:focus-visible]: show the bubble, so pointer and keyboard behave identically.
## HTML
```html
<section class="trs-10 w-full min-h-svh grid place-items-center bg-trs10-bg font-trs10-sans px-5 py-10" id="trs-10-root" style="--v:.286">
<div class="w-[min(460px,100%)] rounded-3xl bg-trs10-card border border-trs10-edge p-9 shadow-[0_24px_70px_-28px_oklch(0_0_0/0.7)]">
<header class="mb-3">
<h3 class="text-lg font-extrabold text-trs10-ink m-0">Loan calculator</h3>
<p class="text-[13px] text-trs10-mut mt-1 m-0">Drag to adjust your loan amount</p>
</header>
<div class="flex items-baseline gap-1.5 mt-5 mb-11">
<span class="text-2xl font-bold text-trs10-acc2">$</span>
<span class="text-[56px] leading-none font-black tracking-tighter text-trs10-ink tabular-nums" id="trs-10-amount">15,000</span>
<span class="text-sm text-trs10-mut self-end mb-2">loan</span>
</div>
<div class="relative pt-14" id="trs-10-wrap">
<div aria-hidden="true" class="absolute top-0 left-[calc(var(--v)*100%)] -translate-x-1/2 flex flex-col items-center pointer-events-none">
<span class="origin-bottom scale-0 rounded-full bg-trs10-acc px-3.5 py-1.5 font-trs10-mono text-[13px] font-semibold text-white whitespace-nowrap shadow-[0_6px_22px_-4px_var(--color-trs10-acc)] transition-transform duration-250 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-[input:active]/wrap:scale-100 motion-reduce:transition-none" id="trs-10-tip">$15,000</span>
</div>
<div class="group/wrap relative">
<div class="h-2 rounded-full bg-white/8"></div>
<div class="absolute left-0 bottom-0 h-2 rounded-full w-[calc(var(--v)*100%)] bg-gradient-to-r from-trs10-acc to-trs10-acc2 shadow-[0_0_18px_-2px_var(--color-trs10-acc)]"></div>
<input type="range" min="1000" max="50000" step="500" value="15000" id="trs-10-inp" aria-label="Loan amount"
class="absolute -bottom-3.5 left-0 w-full h-9 appearance-none bg-transparent outline-none cursor-pointer z-10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-trs10-acc2 rounded-full">
</div>
</div>
<div class="flex justify-between font-trs10-mono text-[11px] text-trs10-mut mt-3.5">
<span>$1K</span><span>$10K</span><span>$25K</span><span>$50K</span>
</div>
<div class="flex gap-2 mt-6" role="group" aria-label="Loan term" id="trs-10-terms">
<button type="button" data-term="12" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">12 mo</button>
<button type="button" data-term="24" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">24 mo</button>
<button type="button" data-term="36" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">36 mo</button>
<button type="button" data-term="60" aria-pressed="true" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">60 mo</button>
</div>
<div class="grid grid-cols-3 mt-6 pt-5 border-t border-trs10-edge text-center">
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Monthly</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-mo">$298</div>
</div>
<div class="border-x border-trs10-edge">
<div class="text-[11px] tracking-wide text-trs10-mut">Rate APR</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1">6.9%</div>
</div>
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Total interest</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-int">$2,880</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}
```
## JavaScript
```js
(function(){
var root = document.getElementById('trs-10-root');
var inp = document.getElementById('trs-10-inp');
var tip = document.getElementById('trs-10-tip');
var amount = document.getElementById('trs-10-amount');
var mo = document.getElementById('trs-10-mo');
var interest = document.getElementById('trs-10-int');
var APR = 0.069;
var term = 60;
function money(n){ return '$' + Math.round(n).toLocaleString('en-US'); }
function short(n){ return n >= 1000 ? '$' + Math.round(n / 1000) + 'K' : '$' + n; }
function update(){
var v = +inp.value;
root.style.setProperty('--v', (v - +inp.min) / (+inp.max - +inp.min));
tip.textContent = short(v);
amount.textContent = v.toLocaleString('en-US');
var r = APR / 12;
var m = v * r * Math.pow(1 + r, term) / (Math.pow(1 + r, term) - 1);
mo.textContent = money(m);
interest.textContent = money(m * term - v);
inp.setAttribute('aria-valuetext', money(v) + ', ' + money(m) + ' per month over ' + term + ' months');
}
document.querySelectorAll('#trs-10-terms button').forEach(function(btn){
btn.addEventListener('click', function(){
document.querySelectorAll('#trs-10-terms button').forEach(function(b){ b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
term = +btn.dataset.term;
update();
});
});
inp.addEventListener('input', update);
update();
})();
```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 CSS Range Slider with Pop-Up Value Bubble (Loan Calculator)
Source: https://codefronts.com/components/tailwind-css-range-sliders/tailwind-range-slider-popup-value-bubble-loan-calculator/
A loan slider whose value bubble springs out of the thumb while you drag, with live monthly payment, total interest and a working term selector. The port deletes four JS event listeners: has-[input:active]: and has-[input:focus-visible]: show the bubble, so pointer and keyboard behave identically.
## HTML
```html
<section class="trs-10 w-full min-h-svh grid place-items-center bg-trs10-bg font-trs10-sans px-5 py-10" id="trs-10-root" style="--v:.286">
<div class="w-[min(460px,100%)] rounded-3xl bg-trs10-card border border-trs10-edge p-9 shadow-[0_24px_70px_-28px_oklch(0_0_0/0.7)]">
<header class="mb-3">
<h3 class="text-lg font-extrabold text-trs10-ink m-0">Loan calculator</h3>
<p class="text-[13px] text-trs10-mut mt-1 m-0">Drag to adjust your loan amount</p>
</header>
<div class="flex items-baseline gap-1.5 mt-5 mb-11">
<span class="text-2xl font-bold text-trs10-acc2">$</span>
<span class="text-[56px] leading-none font-black tracking-tighter text-trs10-ink tabular-nums" id="trs-10-amount">15,000</span>
<span class="text-sm text-trs10-mut self-end mb-2">loan</span>
</div>
<div class="relative pt-14" id="trs-10-wrap">
<div aria-hidden="true" class="absolute top-0 left-[calc(var(--v)*100%)] -translate-x-1/2 flex flex-col items-center pointer-events-none">
<span class="origin-bottom scale-0 rounded-full bg-trs10-acc px-3.5 py-1.5 font-trs10-mono text-[13px] font-semibold text-white whitespace-nowrap shadow-[0_6px_22px_-4px_var(--color-trs10-acc)] transition-transform duration-250 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-[input:active]/wrap:scale-100 motion-reduce:transition-none" id="trs-10-tip">$15,000</span>
</div>
<div class="group/wrap relative">
<div class="h-2 rounded-full bg-white/8"></div>
<div class="absolute left-0 bottom-0 h-2 rounded-full w-[calc(var(--v)*100%)] bg-gradient-to-r from-trs10-acc to-trs10-acc2 shadow-[0_0_18px_-2px_var(--color-trs10-acc)]"></div>
<input type="range" min="1000" max="50000" step="500" value="15000" id="trs-10-inp" aria-label="Loan amount"
class="absolute -bottom-3.5 left-0 w-full h-9 appearance-none bg-transparent outline-none cursor-pointer z-10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-trs10-acc2 rounded-full">
</div>
</div>
<div class="flex justify-between font-trs10-mono text-[11px] text-trs10-mut mt-3.5">
<span>$1K</span><span>$10K</span><span>$25K</span><span>$50K</span>
</div>
<div class="flex gap-2 mt-6" role="group" aria-label="Loan term" id="trs-10-terms">
<button type="button" data-term="12" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">12 mo</button>
<button type="button" data-term="24" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">24 mo</button>
<button type="button" data-term="36" aria-pressed="false" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">36 mo</button>
<button type="button" data-term="60" aria-pressed="true" class="flex-1 min-h-11 rounded-xl border border-trs10-edge text-xs font-semibold text-trs10-mut transition-colors duration-150 hover:text-trs10-ink aria-pressed:border-trs10-acc2 aria-pressed:bg-trs10-acc/20 aria-pressed:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-trs10-acc2">60 mo</button>
</div>
<div class="grid grid-cols-3 mt-6 pt-5 border-t border-trs10-edge text-center">
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Monthly</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-mo">$298</div>
</div>
<div class="border-x border-trs10-edge">
<div class="text-[11px] tracking-wide text-trs10-mut">Rate APR</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1">6.9%</div>
</div>
<div>
<div class="text-[11px] tracking-wide text-trs10-mut">Total interest</div>
<div class="text-base font-bold text-trs10-ink tabular-nums mt-1" id="trs-10-int">$2,880</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trs10-bg: #1e1b4b;
--color-trs10-bg: oklch(0.26 0.1 285);
--color-trs10-card: #2b2760;
--color-trs10-card: oklch(0.31 0.1 285);
--color-trs10-edge: #433d84;
--color-trs10-edge: oklch(0.4 0.1 285);
--color-trs10-acc: #7c3aed;
--color-trs10-acc: oklch(0.54 0.25 295);
--color-trs10-acc2: #a78bfa;
--color-trs10-acc2: oklch(0.75 0.14 295);
--color-trs10-ink: #ede9fe;
--color-trs10-ink: oklch(0.94 0.03 290);
--color-trs10-mut: #a99fe0;
--color-trs10-mut: oklch(0.73 0.08 290);
--font-trs10-sans: 'Outfit', system-ui, sans-serif;
--font-trs10-mono: 'JetBrains Mono', ui-monospace, monospace;
}
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0.286;
}
.trs-10 {
width: 100%;
min-height: 100svh;
}
.trs-10 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
box-shadow: 0 0 14px -2px var(--color-trs10-acc);
cursor: grab;
transition: transform .15s;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.15);
cursor: grabbing;
}
.trs-10 input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 9999px;
background: #fff;
border: 3px solid var(--color-trs10-acc);
cursor: grab;
}
@media (prefers-reduced-motion: reduce) {
.trs-10 input[type="range"]::-webkit-slider-thumb {
transition: none;
}
.trs-10 input[type="range"]:active::-webkit-slider-thumb {
transform: none;
}
}
```
## JavaScript
```js
(function(){
var root = document.getElementById('trs-10-root');
var inp = document.getElementById('trs-10-inp');
var tip = document.getElementById('trs-10-tip');
var amount = document.getElementById('trs-10-amount');
var mo = document.getElementById('trs-10-mo');
var interest = document.getElementById('trs-10-int');
var APR = 0.069;
var term = 60;
function money(n){ return '$' + Math.round(n).toLocaleString('en-US'); }
function short(n){ return n >= 1000 ? '$' + Math.round(n / 1000) + 'K' : '$' + n; }
function update(){
var v = +inp.value;
root.style.setProperty('--v', (v - +inp.min) / (+inp.max - +inp.min));
tip.textContent = short(v);
amount.textContent = v.toLocaleString('en-US');
var r = APR / 12;
var m = v * r * Math.pow(1 + r, term) / (Math.pow(1 + r, term) - 1);
mo.textContent = money(m);
interest.textContent = money(m * term - v);
inp.setAttribute('aria-valuetext', money(v) + ', ' + money(m) + ' per month over ' + term + ' months');
}
document.querySelectorAll('#trs-10-terms button').forEach(function(btn){
btn.addEventListener('click', function(){
document.querySelectorAll('#trs-10-terms button').forEach(function(b){ b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
term = +btn.dataset.term;
update();
});
});
inp.addEventListener('input', update);
update();
})();
```How this works
The pop is CSS, the maths is JS. The bubble sits at left-[calc(var(--v)*100%)] with scale-0 and origin-bottom; the wrapper's has-[input:active]: variant scales it to 1 on a spring curve. The original wired mousedown, touchstart, and two document-level release listeners to do the same job.
Payment is the standard amortisation formula — P·r(1+r)ⁿ / ((1+r)ⁿ−1) with r = APR/12 — computed live for the selected term, plus the total interest, which is the number a borrower actually wants.
Announced, not just displayed. aria-valuetext reads "$15,000 — $298 per month over 60 months", so the slider is usable without seeing the readouts.
Make it yours
- Scale:
min/max/stepon the input, then the tick labels. - APR: the
APRconstant, or make it a second slider and read it in the same function. - Spring: raise the third cubic-bezier parameter past 1.56 for a louder overshoot, or use
ease-outfor none.
Gotchas — read before shipping
- A spring easing overshoots its box. The bubble's wrapper carries top padding so the overshoot is not clipped by the card.
has-[…]needs the input to be a DESCENDANT of the element carrying the variant — the wrapper, not a sibling.- Keep the bubble
pointer-events-noneor it will steal the drag the moment it grows past the thumb.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() drives the pop (Baseline 2023). Without it the bubble stays visible at rest, which is a legitimate variant rather than a failure.