14 / 28
All 28 in 28 Tailwind CSS Input Fields
- 01Tailwind Floating Label Input
- 02Tailwind Underline Sweep Input
- 03Tailwind Neon Glow Input
- 04Tailwind Frosted Glass Input
- 05Tailwind Notched Outline Input
- 06Tailwind Drawn Border Input
- 07Tailwind Inline Validation Input
- 08Tailwind Search Input With Clear
- 09Tailwind Password Strength Meter
- 10Tailwind OTP Code Input
- 11Tailwind Rotating Placeholder Input
- 12Tailwind Tag Chip Input
- 13Tailwind Phone Input With Country
- 14Tailwind Stepper Number Input
- 15Tailwind File Drop Zone
- 16Tailwind Currency Input
- 17Tailwind Native Date Input
- 18Tailwind Time Range Input
- 19Tailwind Colour Picker Input
- 20Tailwind Voice Search Input
- 21Tailwind Email Suggestion Combobox
- 22Tailwind Auto-Grow Textarea
- 23Tailwind Inline Edit Field
- 24Tailwind Range Slider
- 25Tailwind URL Input With Protocol
- 26Tailwind Credit Card Input
- 27Tailwind Password Toggle Input
- 28Tailwind Brutalist Input
Tailwind Stepper Number Input
A quantity control with custom −/+ buttons over a real type="number", so keyboard arrows, validation and form serialisation survive. The CSS original's script reads the wrong dataset key and never steps at all — fixed here.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tif-14 grid min-h-svh w-full place-items-center bg-tif14-bg px-6 py-14 font-tif14-sans" aria-label="Stepper number input demo">
<label class="grid w-full max-w-55 gap-1.5 text-[11px] text-tif14-mut">
<span class="font-semibold">Quantity</span>
<span class="grid grid-cols-[44px_1fr_44px] overflow-hidden rounded-lg border border-white/10 bg-tif14-field transition-colors duration-200 focus-within:border-tif14-acc">
<button type="button" data-tif14-step="-1" aria-label="Decrease quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">−</button>
<input type="number" name="tif14-qty" min="0" max="99" value="1" aria-label="Quantity" data-tif14-input
class="border-0 bg-transparent py-2.5 text-center text-base font-semibold text-tif14-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none">
<button type="button" data-tif14-step="1" aria-label="Increase quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">+</button>
</span>
<span class="sr-only" aria-live="polite" data-tif14-live>Quantity 1</span>
</label>
</div><div class="tif-14 grid min-h-svh w-full place-items-center bg-tif14-bg px-6 py-14 font-tif14-sans" aria-label="Stepper number input demo">
<label class="grid w-full max-w-55 gap-1.5 text-[11px] text-tif14-mut">
<span class="font-semibold">Quantity</span>
<span class="grid grid-cols-[44px_1fr_44px] overflow-hidden rounded-lg border border-white/10 bg-tif14-field transition-colors duration-200 focus-within:border-tif14-acc">
<button type="button" data-tif14-step="-1" aria-label="Decrease quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">−</button>
<input type="number" name="tif14-qty" min="0" max="99" value="1" aria-label="Quantity" data-tif14-input
class="border-0 bg-transparent py-2.5 text-center text-base font-semibold text-tif14-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none">
<button type="button" data-tif14-step="1" aria-label="Increase quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">+</button>
</span>
<span class="sr-only" aria-live="polite" data-tif14-live>Quantity 1</span>
</label>
</div>@import "tailwindcss";
@theme {
--color-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}(function () {
document.querySelectorAll('.tif-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var input = root.querySelector('[data-tif14-input]');
var live = root.querySelector('[data-tif14-live]');
if (!input) return;
root.querySelectorAll('[data-tif14-step]').forEach(function (btn) {
btn.addEventListener('click', function () {
var dir = Number(btn.dataset.tif14Step) || 0;
var min = input.min !== '' ? Number(input.min) : -Infinity;
var max = input.max !== '' ? Number(input.max) : Infinity;
var val = (Number(input.value) || 0) + dir;
input.value = String(Math.min(max, Math.max(min, val)));
if (live) live.textContent = 'Quantity ' + input.value;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
});
})();(function () {
document.querySelectorAll('.tif-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var input = root.querySelector('[data-tif14-input]');
var live = root.querySelector('[data-tif14-live]');
if (!input) return;
root.querySelectorAll('[data-tif14-step]').forEach(function (btn) {
btn.addEventListener('click', function () {
var dir = Number(btn.dataset.tif14Step) || 0;
var min = input.min !== '' ? Number(input.min) : -Infinity;
var max = input.max !== '' ? Number(input.max) : Infinity;
var val = (Number(input.value) || 0) + dir;
input.value = String(Math.min(max, Math.max(min, val)));
if (live) live.textContent = 'Quantity ' + input.value;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
});
})();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-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans: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=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Bug fixed. The original reads
btn.dataset.ifStepwhile the markup writesdata-if-14, sodiris always 0 and the stepper does nothing. The attribute and the read now match. - Buttons grew from 36px to 44px and gained
focus-visiblerings. - An
aria-livemirror and a dispatchedchangeevent were added — the count is now announced, and framework bindings notice it.
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 Stepper Number Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-stepper-number-input/
A quantity control with custom −/+ buttons over a real type="number", so keyboard arrows, validation and form serialisation survive. The CSS original's script reads the wrong dataset key and never steps at all — fixed here.
## HTML
```html
<div class="tif-14 grid min-h-svh w-full place-items-center bg-tif14-bg px-6 py-14 font-tif14-sans" aria-label="Stepper number input demo">
<label class="grid w-full max-w-55 gap-1.5 text-[11px] text-tif14-mut">
<span class="font-semibold">Quantity</span>
<span class="grid grid-cols-[44px_1fr_44px] overflow-hidden rounded-lg border border-white/10 bg-tif14-field transition-colors duration-200 focus-within:border-tif14-acc">
<button type="button" data-tif14-step="-1" aria-label="Decrease quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">−</button>
<input type="number" name="tif14-qty" min="0" max="99" value="1" aria-label="Quantity" data-tif14-input
class="border-0 bg-transparent py-2.5 text-center text-base font-semibold text-tif14-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none">
<button type="button" data-tif14-step="1" aria-label="Increase quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">+</button>
</span>
<span class="sr-only" aria-live="polite" data-tif14-live>Quantity 1</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tif-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var input = root.querySelector('[data-tif14-input]');
var live = root.querySelector('[data-tif14-live]');
if (!input) return;
root.querySelectorAll('[data-tif14-step]').forEach(function (btn) {
btn.addEventListener('click', function () {
var dir = Number(btn.dataset.tif14Step) || 0;
var min = input.min !== '' ? Number(input.min) : -Infinity;
var max = input.max !== '' ? Number(input.max) : Infinity;
var val = (Number(input.value) || 0) + dir;
input.value = String(Math.min(max, Math.max(min, val)));
if (live) live.textContent = 'Quantity ' + input.value;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
});
})();
```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 Stepper Number Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-stepper-number-input/
A quantity control with custom −/+ buttons over a real type="number", so keyboard arrows, validation and form serialisation survive. The CSS original's script reads the wrong dataset key and never steps at all — fixed here.
## HTML
```html
<div class="tif-14 grid min-h-svh w-full place-items-center bg-tif14-bg px-6 py-14 font-tif14-sans" aria-label="Stepper number input demo">
<label class="grid w-full max-w-55 gap-1.5 text-[11px] text-tif14-mut">
<span class="font-semibold">Quantity</span>
<span class="grid grid-cols-[44px_1fr_44px] overflow-hidden rounded-lg border border-white/10 bg-tif14-field transition-colors duration-200 focus-within:border-tif14-acc">
<button type="button" data-tif14-step="-1" aria-label="Decrease quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">−</button>
<input type="number" name="tif14-qty" min="0" max="99" value="1" aria-label="Quantity" data-tif14-input
class="border-0 bg-transparent py-2.5 text-center text-base font-semibold text-tif14-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none">
<button type="button" data-tif14-step="1" aria-label="Increase quantity"
class="cursor-pointer border-0 bg-tif14-acc/6 text-lg font-bold text-tif14-acc2 transition-colors duration-150 hover:bg-tif14-acc/18 hover:text-white active:bg-tif14-acc/28 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tif14-acc">+</button>
</span>
<span class="sr-only" aria-live="polite" data-tif14-live>Quantity 1</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif14-bg: #12121a;
--color-tif14-bg: oklch(0.19 0.015 285);
--color-tif14-field: #1a1a22;
--color-tif14-field: oklch(0.23 0.015 285);
--color-tif14-ink: #f0eeff;
--color-tif14-ink: oklch(0.95 0.02 290);
--color-tif14-mut: #b8b6d4;
--color-tif14-mut: oklch(0.77 0.03 290);
--color-tif14-acc: #14b8a6;
--color-tif14-acc: oklch(0.7 0.13 180);
--color-tif14-acc2: #5eead4;
--color-tif14-acc2: oklch(0.85 0.12 178);
--font-tif14-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-14 {
width: 100%;
}
.tif-14 ::selection {
background: var(--color-tif14-acc);
color: #12121a;
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tif-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var input = root.querySelector('[data-tif14-input]');
var live = root.querySelector('[data-tif14-live]');
if (!input) return;
root.querySelectorAll('[data-tif14-step]').forEach(function (btn) {
btn.addEventListener('click', function () {
var dir = Number(btn.dataset.tif14Step) || 0;
var min = input.min !== '' ? Number(input.min) : -Infinity;
var max = input.max !== '' ? Number(input.max) : Infinity;
var val = (Number(input.value) || 0) + dir;
input.value = String(Math.min(max, Math.max(min, val)));
if (live) live.textContent = 'Quantity ' + input.value;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
});
})();
```How this works
The buttons carry the direction. data-tif14-step="-1" and "1" are read, added, clamped against the input's own min/max, and written back:
var dir = Number(btn.dataset.tif14Step) || 0;
var val = (Number(input.value) || 0) + dir;
input.value = String(Math.min(max, Math.max(min, val)));The real input is the point. Everything free — arrow keys, the mobile numeric keypad, ValidityState, paste, form submit — comes from not replacing the native control with two divs and a span.
Make it yours
- Bounds:
min,max,stepon the input. No JS edit needed. - Hold-to-repeat:
pointerdown+setInterval, cleared onpointerupandpointercancel. - Colour:
--color-tif14-accdrives button fill, text and focus border. - Size:
grid-cols-[44px_1fr_44px]; do not go below 44.
Gotchas — read before shipping
type="number"silently drops pasted"1,234". Parse before assigning if locale input matters.- Hold-to-repeat leaks its timer unless you clear on
pointercancelas well aspointerup. - Firefox changes a focused number input on scroll-wheel. Blur on
wheelif that is unwanted.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
type=number, dataset and grid are universal; the floor is Tailwind v4's sheet.