27 / 30
All 30 in 30 Tailwind CSS Login Forms
- 01Tailwind Split-Screen Brand Hero Login Form
- 02Tailwind Multi-Tenant Workspace Account Switcher
- 03Tailwind Floating Label Login Form with peer Variants
- 04Tailwind Web3 Wallet Connect Login Grid
- 05Tailwind Minimalist Fintech Login Card with Layered Shadow
- 06Tailwind OAuth Social Login Form with Brand SVG Buttons
- 07Tailwind Passwordless Magic Link Login with data-state Machine
- 08Tailwind Aurora Glow Login Card with Drifting Blur Blobs
- 09Tailwind Neon Synthwave Arcade Login Form
- 10Tailwind Sliding Login and Sign Up Panel with peer-checked
- 11Tailwind 3D Flip Login Card with Password Reset Back Face
- 12Tailwind Terminal CLI Prompt Login Form
- 13Tailwind Floating Pill Login Widget Docked Over App Content
- 14Tailwind Gradient Border Login Card with background-clip
- 15Tailwind Slide-Over Side Panel Login without JavaScript
- 16Tailwind Social-First Sign Up with Brand-Compliant Buttons
- 17Tailwind Animated Mascot Login that Covers Its Eyes
- 18Tailwind Accessible Compact Login Modal with Scale-In
- 19Tailwind Glassmorphism Login Card with backdrop-filter
- 20Tailwind Multi-Step Login Wizard with Radio-Driven Track
- 21Tailwind Animated Conic Gradient Border Login Card
- 22Tailwind Secure Vault Login with user-invalid Error State
- 23Tailwind Neo-Brutalist Login Form with Hard Offset Shadow
- 24Tailwind Show Hide Password Toggle with aria-pressed
- 25Tailwind OTP Code Input Grid with Auto-Advance
- 26Tailwind Password Strength Meter with data-score Variants
- 27Tailwind Numeric PIN Pad Login for Kiosks and POS
- 28Tailwind Inline Form Validation with user-valid and user-invalid
- 29Tailwind Particle Constellation Login over Canvas
- 30Tailwind Biometric Face ID Prompt with Pulse Ripples
Tailwind Numeric PIN Pad Login for Kiosks and POS
A numeric PIN entry for kiosks, POS terminals and mobile web: a rigid 3×4 keypad of big round keys feeding a row of masked dots, with a press that scales and a delete key. The port keeps the script to state and moves the filled-dot look to data-[on]:, so the handler sets an attribute instead of toggling a class name it has to remember.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tlf-27 w-full min-h-svh grid place-items-center bg-tlf27-stage font-tlf27-sans p-6">
<form novalidate class="tlf-27-form w-[min(340px,100%)] p-8 text-center bg-tlf27-card border border-tlf27-edge rounded-[22px] shadow-[0_30px_70px_-44px_black]">
<h1 class="mb-5 text-[19px] font-semibold text-tlf27-ink">Enter your PIN</h1>
<div aria-hidden="true" class="tlf-27-dots flex justify-center gap-4 mb-7">
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
</div>
<input class="tlf-27-value" type="hidden" name="pin">
<p class="tlf-27-status sr-only" role="status" aria-live="polite">0 of 4 digits entered</p>
<div role="group" aria-label="PIN keypad" class="grid grid-cols-3 gap-3.5 justify-items-center">
<button type="button" data-d="1" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">1</button>
<button type="button" data-d="2" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">2</button>
<button type="button" data-d="3" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">3</button>
<button type="button" data-d="4" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">4</button>
<button type="button" data-d="5" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">5</button>
<button type="button" data-d="6" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">6</button>
<button type="button" data-d="7" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">7</button>
<button type="button" data-d="8" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">8</button>
<button type="button" data-d="9" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">9</button>
<span aria-hidden="true" class="size-16"></span>
<button type="button" data-d="0" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">0</button>
<button type="button" data-del aria-label="Delete last digit" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-mut text-xl transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">⌫</button>
</div>
</form>
</section><section class="tlf-27 w-full min-h-svh grid place-items-center bg-tlf27-stage font-tlf27-sans p-6">
<form novalidate class="tlf-27-form w-[min(340px,100%)] p-8 text-center bg-tlf27-card border border-tlf27-edge rounded-[22px] shadow-[0_30px_70px_-44px_black]">
<h1 class="mb-5 text-[19px] font-semibold text-tlf27-ink">Enter your PIN</h1>
<div aria-hidden="true" class="tlf-27-dots flex justify-center gap-4 mb-7">
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
</div>
<input class="tlf-27-value" type="hidden" name="pin">
<p class="tlf-27-status sr-only" role="status" aria-live="polite">0 of 4 digits entered</p>
<div role="group" aria-label="PIN keypad" class="grid grid-cols-3 gap-3.5 justify-items-center">
<button type="button" data-d="1" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">1</button>
<button type="button" data-d="2" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">2</button>
<button type="button" data-d="3" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">3</button>
<button type="button" data-d="4" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">4</button>
<button type="button" data-d="5" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">5</button>
<button type="button" data-d="6" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">6</button>
<button type="button" data-d="7" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">7</button>
<button type="button" data-d="8" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">8</button>
<button type="button" data-d="9" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">9</button>
<span aria-hidden="true" class="size-16"></span>
<button type="button" data-d="0" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">0</button>
<button type="button" data-del aria-label="Delete last digit" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-mut text-xl transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">⌫</button>
</div>
</form>
</section>@import "tailwindcss";
@theme {
--color-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}@import "tailwindcss";
@theme {
--color-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}const section = document.querySelector('.tlf-27');
const dots = [...section.querySelectorAll('.tlf-27-dots span')];
const value = section.querySelector('.tlf-27-value');
const status = section.querySelector('.tlf-27-status');
const MAX = dots.length;
let pin = '';
function render() {
dots.forEach((d, i) => {
if (i < pin.length) d.setAttribute('data-on', '');
else d.removeAttribute('data-on');
});
value.value = pin;
status.textContent = pin.length + ' of ' + MAX + ' digits entered';
}
section.querySelectorAll('.tlf-27-key').forEach((key) => {
key.addEventListener('click', () => {
if (key.hasAttribute('data-del')) pin = pin.slice(0, -1);
else if (pin.length < MAX) pin += key.dataset.d;
render();
if (pin.length === MAX) setTimeout(() => { pin = ''; render(); }, 600);
});
});
render();const section = document.querySelector('.tlf-27');
const dots = [...section.querySelectorAll('.tlf-27-dots span')];
const value = section.querySelector('.tlf-27-value');
const status = section.querySelector('.tlf-27-status');
const MAX = dots.length;
let pin = '';
function render() {
dots.forEach((d, i) => {
if (i < pin.length) d.setAttribute('data-on', '');
else d.removeAttribute('data-on');
});
value.value = pin;
status.textContent = pin.length + ' of ' + MAX + ' digits entered';
}
section.querySelectorAll('.tlf-27-key').forEach((key) => {
key.addEventListener('click', () => {
if (key.hasAttribute('data-del')) pin = pin.slice(0, -1);
else if (pin.length < MAX) pin += key.dataset.d;
render();
if (pin.length === MAX) setTimeout(() => { pin = ''; render(); }, 600);
});
});
render();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-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}@import "tailwindcss";
@theme {
--color-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}<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=Figtree: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=Figtree:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
data-[on]:instead of a.is-onclass is a small but real improvement: the attribute name lives in the handler and in the variant, both visible, with no stylesheet in between to drift.scale-115exists in v4's scale (and arbitrary values cover the rest), so the dot's 1.15× pop needs no bracket value.- An
sr-onlylive region announcing "2 of 4 digits entered" was added: the original conveyed PIN progress entirely visually, which on a kiosk is the one place that matters most. - UI change from the CSS original: Figtree replaces the system stack, the delete key's ink lifted from
#98a0b4tooklch(0.75 0.018 265), key borders brightened so the pad is visible in daylight on a kiosk screen, and the auto-clear delay went 500ms → 600ms so the fourth dot is seen before it resets.
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 Numeric PIN Pad Login for Kiosks and POS
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-numeric-pin-pad-login-kiosk-pos/
A numeric PIN entry for kiosks, POS terminals and mobile web: a rigid 3×4 keypad of big round keys feeding a row of masked dots, with a press that scales and a delete key. The port keeps the script to state and moves the filled-dot look to data-[on]:, so the handler sets an attribute instead of toggling a class name it has to remember.
## HTML
```html
<section class="tlf-27 w-full min-h-svh grid place-items-center bg-tlf27-stage font-tlf27-sans p-6">
<form novalidate class="tlf-27-form w-[min(340px,100%)] p-8 text-center bg-tlf27-card border border-tlf27-edge rounded-[22px] shadow-[0_30px_70px_-44px_black]">
<h1 class="mb-5 text-[19px] font-semibold text-tlf27-ink">Enter your PIN</h1>
<div aria-hidden="true" class="tlf-27-dots flex justify-center gap-4 mb-7">
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
</div>
<input class="tlf-27-value" type="hidden" name="pin">
<p class="tlf-27-status sr-only" role="status" aria-live="polite">0 of 4 digits entered</p>
<div role="group" aria-label="PIN keypad" class="grid grid-cols-3 gap-3.5 justify-items-center">
<button type="button" data-d="1" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">1</button>
<button type="button" data-d="2" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">2</button>
<button type="button" data-d="3" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">3</button>
<button type="button" data-d="4" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">4</button>
<button type="button" data-d="5" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">5</button>
<button type="button" data-d="6" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">6</button>
<button type="button" data-d="7" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">7</button>
<button type="button" data-d="8" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">8</button>
<button type="button" data-d="9" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">9</button>
<span aria-hidden="true" class="size-16"></span>
<button type="button" data-d="0" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">0</button>
<button type="button" data-del aria-label="Delete last digit" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-mut text-xl transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">⌫</button>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
```
## JavaScript
```js
const section = document.querySelector('.tlf-27');
const dots = [...section.querySelectorAll('.tlf-27-dots span')];
const value = section.querySelector('.tlf-27-value');
const status = section.querySelector('.tlf-27-status');
const MAX = dots.length;
let pin = '';
function render() {
dots.forEach((d, i) => {
if (i < pin.length) d.setAttribute('data-on', '');
else d.removeAttribute('data-on');
});
value.value = pin;
status.textContent = pin.length + ' of ' + MAX + ' digits entered';
}
section.querySelectorAll('.tlf-27-key').forEach((key) => {
key.addEventListener('click', () => {
if (key.hasAttribute('data-del')) pin = pin.slice(0, -1);
else if (pin.length < MAX) pin += key.dataset.d;
render();
if (pin.length === MAX) setTimeout(() => { pin = ''; render(); }, 600);
});
});
render();
```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 Numeric PIN Pad Login for Kiosks and POS
Source: https://codefronts.com/components/tailwind-css-login-forms/tailwind-numeric-pin-pad-login-kiosk-pos/
A numeric PIN entry for kiosks, POS terminals and mobile web: a rigid 3×4 keypad of big round keys feeding a row of masked dots, with a press that scales and a delete key. The port keeps the script to state and moves the filled-dot look to data-[on]:, so the handler sets an attribute instead of toggling a class name it has to remember.
## HTML
```html
<section class="tlf-27 w-full min-h-svh grid place-items-center bg-tlf27-stage font-tlf27-sans p-6">
<form novalidate class="tlf-27-form w-[min(340px,100%)] p-8 text-center bg-tlf27-card border border-tlf27-edge rounded-[22px] shadow-[0_30px_70px_-44px_black]">
<h1 class="mb-5 text-[19px] font-semibold text-tlf27-ink">Enter your PIN</h1>
<div aria-hidden="true" class="tlf-27-dots flex justify-center gap-4 mb-7">
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
<span class="size-3.5 rounded-full border-2 border-tlf27-dot bg-transparent transition-[background-color,border-color,scale] duration-150 data-[on]:bg-tlf27-acc data-[on]:border-tlf27-acc data-[on]:scale-115 motion-reduce:transition-colors"></span>
</div>
<input class="tlf-27-value" type="hidden" name="pin">
<p class="tlf-27-status sr-only" role="status" aria-live="polite">0 of 4 digits entered</p>
<div role="group" aria-label="PIN keypad" class="grid grid-cols-3 gap-3.5 justify-items-center">
<button type="button" data-d="1" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">1</button>
<button type="button" data-d="2" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">2</button>
<button type="button" data-d="3" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">3</button>
<button type="button" data-d="4" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">4</button>
<button type="button" data-d="5" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">5</button>
<button type="button" data-d="6" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">6</button>
<button type="button" data-d="7" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">7</button>
<button type="button" data-d="8" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">8</button>
<button type="button" data-d="9" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">9</button>
<span aria-hidden="true" class="size-16"></span>
<button type="button" data-d="0" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-ink text-2xl font-medium transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">0</button>
<button type="button" data-del aria-label="Delete last digit" class="tlf-27-key size-16 rounded-full cursor-pointer bg-tlf27-key border-[1.5px] border-tlf27-keyedge text-tlf27-mut text-xl transition-[background-color,border-color,scale] duration-100 hover:bg-tlf27-keyhover hover:border-tlf27-dot active:scale-90 active:bg-tlf27-press active:border-tlf27-acc focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tlf27-ring motion-reduce:transition-colors motion-reduce:active:scale-100">⌫</button>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tlf27-stage: #12151d;
--color-tlf27-stage: oklch(0.18 0.012 265);
--color-tlf27-card: #1a1f2b;
--color-tlf27-card: oklch(0.23 0.014 265);
--color-tlf27-edge: #262c3a;
--color-tlf27-edge: oklch(0.28 0.016 265);
--color-tlf27-key: #212635;
--color-tlf27-key: oklch(0.26 0.016 265);
--color-tlf27-keyhover: #2b3244;
--color-tlf27-keyhover: oklch(0.31 0.018 265);
--color-tlf27-keyedge: #333b4d;
--color-tlf27-keyedge: oklch(0.33 0.018 265);
--color-tlf27-dot: #4a5468;
--color-tlf27-dot: oklch(0.43 0.02 265);
--color-tlf27-ink: #e8ebf3;
--color-tlf27-ink: oklch(0.93 0.008 265);
--color-tlf27-mut: #a8b0c4;
--color-tlf27-mut: oklch(0.75 0.018 265);
--color-tlf27-acc: #7b90e8;
--color-tlf27-acc: oklch(0.68 0.15 265);
--color-tlf27-press: #2f3a5e;
--color-tlf27-press: oklch(0.68 0.15 265 / 0.25);
--color-tlf27-ring: #a3b2ef;
--color-tlf27-ring: oklch(0.68 0.15 265 / 0.6);
--font-tlf27-sans: 'Figtree', system-ui, sans-serif;
}
.tlf-27 {
width: 100%;
min-height: 100svh;
color-scheme: dark;
}
```
## JavaScript
```js
const section = document.querySelector('.tlf-27');
const dots = [...section.querySelectorAll('.tlf-27-dots span')];
const value = section.querySelector('.tlf-27-value');
const status = section.querySelector('.tlf-27-status');
const MAX = dots.length;
let pin = '';
function render() {
dots.forEach((d, i) => {
if (i < pin.length) d.setAttribute('data-on', '');
else d.removeAttribute('data-on');
});
value.value = pin;
status.textContent = pin.length + ' of ' + MAX + ' digits entered';
}
section.querySelectorAll('.tlf-27-key').forEach((key) => {
key.addEventListener('click', () => {
if (key.hasAttribute('data-del')) pin = pin.slice(0, -1);
else if (pin.length < MAX) pin += key.dataset.d;
render();
if (pin.length === MAX) setTimeout(() => { pin = ''; render(); }, 600);
});
});
render();
```How this works
The grid is the design. Three equal columns, twelve cells, one of them deliberately empty. Nothing is positioned; the keypad cannot drift out of alignment because the grid does not allow it.
Dots read an attribute. render() sets or removes data-on per dot and data-[on]: does the rest — no class-name string shared between the script and a stylesheet, which is the small bug this port removes.
Real buttons, real labels. Every key is a <button type="button">, so the pad is tabbable and Enter/Space work; the delete key carries an aria-label because ⌫ announces as nothing useful.
Make it yours
- PIN length: add dots, and the script reads the count from the DOM.
- Key size:
size-16on the keys and the spacer together. - Accent:
--color-tlf27-accdrives filled dots, the press wash and the rings. - Fill the empty cell with a biometric or an SOS key — the grid already reserves it.
Gotchas — read before shipping
- Keys must be buttons. A
<div>keypad needs tabindex, keydown and role work to reach parity, and kiosks are exactly where that gets skipped. - Always ship a delete key. Forcing a full re-entry on one mistyped digit is the fastest way to get a PIN pad replaced.
- Keep the spacer element. Removing it reflows 0 and ⌫ into the wrong cells, which users notice instantly because phone keypads are muscle memory.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Grid, data-attribute variants and transforms are universally supported; entry needs the handler.