02 / 20
All 20 in 20 Tailwind CSS Kbd Keys
- 01Tailwind CSS Kbd Tag Styling for Inline Keyboard Keys
- 02Tailwind 3D Keycap Button with Press-Down Effect
- 03Tailwind Keyboard Shortcut Combo with Plus Separators
- 04Flat Minimalist Tailwind Kbd Key for Body Text
- 05Responsive Tailwind Kbd Sizing with Em Units
- 06Tailwind Mac Cmd vs Windows Ctrl Shortcut Toggle
- 07Tailwind Arrow Keys and Navigation Key Layout
- 08Auto-Detect OS Keyboard Shortcuts (Cmd or Ctrl) in Tailwind
- 09Tailwind Keyboard Layout with Function Keys and Space Bar
- 10Tailwind Icon Keycaps with Inline SVG
- 11Tailwind Search Input with Cmd+K Shortcut Hint
- 12Tailwind Keyboard Shortcuts Cheat Sheet Grid
- 13Tailwind Tooltip with Keyboard Shortcut Hint
- 14Tailwind Keyboard Shortcut Settings Table
- 15Live Key Press Visualizer with Tailwind CSS
- 16Neumorphic Keycap Design in Tailwind CSS
- 17Glassmorphism Keyboard Keys in Tailwind CSS
- 18Retro Typewriter Round Keys in Tailwind CSS
- 19Terminal-Style Monospace Kbd Keys in Tailwind
- 20Dark Mode Kbd Keys with Tailwind CSS Variables
Tailwind 3D Keycap Button with Press-Down Effect
A chunky 3D keycap whose wall is a solid 0_6px_0 shadow that collapses to 0_2px_0 as the face travels down translate-y-1 — 4px of travel, perfectly synchronised, so the key looks pressed rather than sunk. Each cap is a real <button> wrapping a <kbd>, so it responds to pointer and a held Space bar.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="kbd-02 relative w-full min-h-svh scheme-light kbd02-dark:scheme-dark bg-kbd02-bg text-kbd02-ink font-kbd02-sans" aria-label="3D keycap press-down demo">
<div class="grid min-h-svh place-items-center px-5 pt-20 pb-12">
<div class="grid w-full max-w-[36rem] justify-items-center gap-7 text-center">
<p class="text-sm text-kbd02-mut">Press and hold a cap — or Tab to one and hold <strong class="font-semibold text-kbd02-ink">Space</strong></p>
<div class="flex flex-wrap justify-center gap-3 rounded-[22px] bg-kbd02-tray p-5 pb-6 shadow-[inset_0_2px_6px_var(--color-kbd02-drop)]">
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-accwall from-kbd02-acctop to-kbd02-acc text-kbd02-accink shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_6px_0_var(--color-kbd02-accwall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_2px_0_var(--color-kbd02-accwall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-base">Esc</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">Q</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">W</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">E</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">R</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-36 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] items-end justify-items-start pb-2.5 text-sm">Shift</kbd></button>
</div>
<p class="font-mono text-xs tracking-wide text-kbd02-mut">travel 4px · wall 6px → 2px · 90ms</p>
</div>
</div>
<div class="absolute top-3.5 right-4 z-10">
<input id="kbd-02-theme" type="checkbox" class="peer sr-only">
<label for="kbd-02-theme" class="inline-flex min-h-11 cursor-pointer select-none items-center gap-2.5 rounded-full border border-current/55 px-4 font-sans text-[11px] font-bold uppercase leading-none tracking-[0.16em] text-kbd02-ink transition-colors hover:border-current peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-kbd02-acc">
<span aria-hidden="true" class="size-3 flex-none rounded-full bg-current transition-[background-color,box-shadow] duration-300 kbd02-dark:bg-transparent kbd02-dark:shadow-[inset_-4px_-2px_0_0_currentColor] motion-reduce:transition-none"></span>
<span class="kbd02-dark:hidden">Light</span><span class="hidden kbd02-dark:inline">Dark</span><span class="sr-only"> theme</span>
</label>
</div>
</section><section class="kbd-02 relative w-full min-h-svh scheme-light kbd02-dark:scheme-dark bg-kbd02-bg text-kbd02-ink font-kbd02-sans" aria-label="3D keycap press-down demo">
<div class="grid min-h-svh place-items-center px-5 pt-20 pb-12">
<div class="grid w-full max-w-[36rem] justify-items-center gap-7 text-center">
<p class="text-sm text-kbd02-mut">Press and hold a cap — or Tab to one and hold <strong class="font-semibold text-kbd02-ink">Space</strong></p>
<div class="flex flex-wrap justify-center gap-3 rounded-[22px] bg-kbd02-tray p-5 pb-6 shadow-[inset_0_2px_6px_var(--color-kbd02-drop)]">
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-accwall from-kbd02-acctop to-kbd02-acc text-kbd02-accink shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_6px_0_var(--color-kbd02-accwall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_2px_0_var(--color-kbd02-accwall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-base">Esc</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">Q</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">W</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">E</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">R</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-36 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] items-end justify-items-start pb-2.5 text-sm">Shift</kbd></button>
</div>
<p class="font-mono text-xs tracking-wide text-kbd02-mut">travel 4px · wall 6px → 2px · 90ms</p>
</div>
</div>
<div class="absolute top-3.5 right-4 z-10">
<input id="kbd-02-theme" type="checkbox" class="peer sr-only">
<label for="kbd-02-theme" class="inline-flex min-h-11 cursor-pointer select-none items-center gap-2.5 rounded-full border border-current/55 px-4 font-sans text-[11px] font-bold uppercase leading-none tracking-[0.16em] text-kbd02-ink transition-colors hover:border-current peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-kbd02-acc">
<span aria-hidden="true" class="size-3 flex-none rounded-full bg-current transition-[background-color,box-shadow] duration-300 kbd02-dark:bg-transparent kbd02-dark:shadow-[inset_-4px_-2px_0_0_currentColor] motion-reduce:transition-none"></span>
<span class="kbd02-dark:hidden">Light</span><span class="hidden kbd02-dark:inline">Dark</span><span class="sr-only"> theme</span>
</label>
</div>
</section>@import "tailwindcss";
/* Theme state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}@import "tailwindcss";
/* Theme state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}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 state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}@import "tailwindcss";
/* Theme state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.kbd-02__key:activebecamegroup-active:utilities on the cap inside a button, so the state rides on the element that can actually receive it.- Both shadow states are full arbitrary values — Tailwind cannot interpolate partial shadows, so the press is written out once each way.
- UI upgrade: caps are now buttons, fixing the CSS original's promise that holding Enter on a
tabindexkbd would press it (it never did). - UI upgrade: an accent Esc cap, a recessed tray, a convex gradient face and a bottom-left legend on Shift make the board read as real hardware.
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 3D Keycap Button with Press-Down Effect
Source: https://codefronts.com/snippets/tailwind-css-kbd-keys/tailwind-3d-keycap-button-press-down-effect/
A chunky 3D keycap whose wall is a solid 0_6px_0 shadow that collapses to 0_2px_0 as the face travels down translate-y-1 — 4px of travel, perfectly synchronised, so the key looks pressed rather than sunk. Each cap is a real <button> wrapping a <kbd>, so it responds to pointer and a held Space bar.
## HTML
```html
<section class="kbd-02 relative w-full min-h-svh scheme-light kbd02-dark:scheme-dark bg-kbd02-bg text-kbd02-ink font-kbd02-sans" aria-label="3D keycap press-down demo">
<div class="grid min-h-svh place-items-center px-5 pt-20 pb-12">
<div class="grid w-full max-w-[36rem] justify-items-center gap-7 text-center">
<p class="text-sm text-kbd02-mut">Press and hold a cap — or Tab to one and hold <strong class="font-semibold text-kbd02-ink">Space</strong></p>
<div class="flex flex-wrap justify-center gap-3 rounded-[22px] bg-kbd02-tray p-5 pb-6 shadow-[inset_0_2px_6px_var(--color-kbd02-drop)]">
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-accwall from-kbd02-acctop to-kbd02-acc text-kbd02-accink shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_6px_0_var(--color-kbd02-accwall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_2px_0_var(--color-kbd02-accwall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-base">Esc</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">Q</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">W</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">E</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">R</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-36 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] items-end justify-items-start pb-2.5 text-sm">Shift</kbd></button>
</div>
<p class="font-mono text-xs tracking-wide text-kbd02-mut">travel 4px · wall 6px → 2px · 90ms</p>
</div>
</div>
<div class="absolute top-3.5 right-4 z-10">
<input id="kbd-02-theme" type="checkbox" class="peer sr-only">
<label for="kbd-02-theme" class="inline-flex min-h-11 cursor-pointer select-none items-center gap-2.5 rounded-full border border-current/55 px-4 font-sans text-[11px] font-bold uppercase leading-none tracking-[0.16em] text-kbd02-ink transition-colors hover:border-current peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-kbd02-acc">
<span aria-hidden="true" class="size-3 flex-none rounded-full bg-current transition-[background-color,box-shadow] duration-300 kbd02-dark:bg-transparent kbd02-dark:shadow-[inset_-4px_-2px_0_0_currentColor] motion-reduce:transition-none"></span>
<span class="kbd02-dark:hidden">Light</span><span class="hidden kbd02-dark:inline">Dark</span><span class="sr-only"> theme</span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Theme state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}
```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 3D Keycap Button with Press-Down Effect
Source: https://codefronts.com/snippets/tailwind-css-kbd-keys/tailwind-3d-keycap-button-press-down-effect/
A chunky 3D keycap whose wall is a solid 0_6px_0 shadow that collapses to 0_2px_0 as the face travels down translate-y-1 — 4px of travel, perfectly synchronised, so the key looks pressed rather than sunk. Each cap is a real <button> wrapping a <kbd>, so it responds to pointer and a held Space bar.
## HTML
```html
<section class="kbd-02 relative w-full min-h-svh scheme-light kbd02-dark:scheme-dark bg-kbd02-bg text-kbd02-ink font-kbd02-sans" aria-label="3D keycap press-down demo">
<div class="grid min-h-svh place-items-center px-5 pt-20 pb-12">
<div class="grid w-full max-w-[36rem] justify-items-center gap-7 text-center">
<p class="text-sm text-kbd02-mut">Press and hold a cap — or Tab to one and hold <strong class="font-semibold text-kbd02-ink">Space</strong></p>
<div class="flex flex-wrap justify-center gap-3 rounded-[22px] bg-kbd02-tray p-5 pb-6 shadow-[inset_0_2px_6px_var(--color-kbd02-drop)]">
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-accwall from-kbd02-acctop to-kbd02-acc text-kbd02-accink shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_6px_0_var(--color-kbd02-accwall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-acctop),0_2px_0_var(--color-kbd02-accwall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-base">Esc</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">Q</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">W</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">E</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-16 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] place-items-center text-lg">R</kbd></button>
<button type="button" class="group rounded-[14px] outline-none focus-visible:outline-3 focus-visible:outline-offset-4 focus-visible:outline-kbd02-acc"><kbd class="grid h-16 min-w-36 rounded-[12px] border bg-linear-to-b px-4 font-kbd02-sans font-semibold transition-[translate,box-shadow] duration-[90ms] ease-out group-active:translate-y-1 motion-reduce:transition-none border-kbd02-rim from-kbd02-top to-kbd02-cap text-kbd02-capink shadow-[inset_0_1px_0_var(--color-kbd02-top),0_6px_0_var(--color-kbd02-wall),0_12px_18px_-6px_var(--color-kbd02-drop)] group-active:shadow-[inset_0_1px_0_var(--color-kbd02-top),0_2px_0_var(--color-kbd02-wall),0_4px_8px_-4px_var(--color-kbd02-drop)] items-end justify-items-start pb-2.5 text-sm">Shift</kbd></button>
</div>
<p class="font-mono text-xs tracking-wide text-kbd02-mut">travel 4px · wall 6px → 2px · 90ms</p>
</div>
</div>
<div class="absolute top-3.5 right-4 z-10">
<input id="kbd-02-theme" type="checkbox" class="peer sr-only">
<label for="kbd-02-theme" class="inline-flex min-h-11 cursor-pointer select-none items-center gap-2.5 rounded-full border border-current/55 px-4 font-sans text-[11px] font-bold uppercase leading-none tracking-[0.16em] text-kbd02-ink transition-colors hover:border-current peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-kbd02-acc">
<span aria-hidden="true" class="size-3 flex-none rounded-full bg-current transition-[background-color,box-shadow] duration-300 kbd02-dark:bg-transparent kbd02-dark:shadow-[inset_-4px_-2px_0_0_currentColor] motion-reduce:transition-none"></span>
<span class="kbd02-dark:hidden">Light</span><span class="hidden kbd02-dark:inline">Dark</span><span class="sr-only"> theme</span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Theme state: follows the OS; the corner switch flips it for THIS demo only. */
@custom-variant kbd02-dark {
@media (prefers-color-scheme: dark) {
&:where(.kbd-02:not(:has(#kbd-02-theme:checked)), .kbd-02:not(:has(#kbd-02-theme:checked)) *) {
@slot;
}
}
@media (prefers-color-scheme: light) {
&:where(.kbd-02:has(#kbd-02-theme:checked), .kbd-02:has(#kbd-02-theme:checked) *) {
@slot;
}
}
}
/* Every colour is light-dark(day, night): the root's scheme-light / scheme-dark utility picks the side. */
@theme {
--color-kbd02-bg: light-dark(#ebe6dc, #121418);
--color-kbd02-bg: light-dark(oklch(0.926 0.014 84.6), oklch(0.191 0.009 264.3));
--color-kbd02-ink: light-dark(#24211c, #e8eaee);
--color-kbd02-ink: light-dark(oklch(0.249 0.01 80.6), oklch(0.937 0.006 264.5));
--color-kbd02-mut: light-dark(#5d564b, #9ba2ae);
--color-kbd02-mut: light-dark(oklch(0.456 0.02 79.2), oklch(0.71 0.019 261.3));
--color-kbd02-tray: light-dark(#dcd5c7, #0b0d10);
--color-kbd02-tray: light-dark(oklch(0.875 0.02 84.6), oklch(0.158 0.007 258.4));
--color-kbd02-cap: light-dark(#f6f2ea, #2a2e36);
--color-kbd02-cap: light-dark(oklch(0.962 0.011 84.6), oklch(0.301 0.016 264.3));
--color-kbd02-top: light-dark(#ffffff, #3a404b);
--color-kbd02-top: light-dark(oklch(1 0 0), oklch(0.37 0.021 262.6));
--color-kbd02-rim: light-dark(#d3ccbf, #474d59);
--color-kbd02-rim: light-dark(oklch(0.847 0.019 83.1), oklch(0.419 0.021 264.3));
--color-kbd02-wall: light-dark(#b9af9d, #05060a);
--color-kbd02-wall: light-dark(oklch(0.757 0.028 82.4), oklch(0.123 0.011 271.4));
--color-kbd02-drop: light-dark(#6b5f4c66, #00000099);
--color-kbd02-drop: light-dark(oklch(0.492 0.033 79.2 / 0.4), oklch(0 0 0 / 0.6));
--color-kbd02-capink: light-dark(#2b2720, #eef0f4);
--color-kbd02-capink: light-dark(oklch(0.275 0.014 81.7), oklch(0.955 0.006 264.5));
--color-kbd02-acc: light-dark(#d9541f, #f0703a);
--color-kbd02-acc: light-dark(oklch(0.615 0.178 39.6), oklch(0.69 0.172 41.8));
--color-kbd02-acctop: light-dark(#f2804e, #ff9563);
--color-kbd02-acctop: light-dark(oklch(0.719 0.154 43.3), oklch(0.772 0.143 45.3));
--color-kbd02-accwall: light-dark(#9b3510, #8a300c);
--color-kbd02-accwall: light-dark(oklch(0.473 0.144 38.1), oklch(0.436 0.13 39.2));
--color-kbd02-accink: light-dark(#ffffff, #ffffff);
--color-kbd02-accink: light-dark(oklch(1 0 0), oklch(1 0 0));
--font-kbd02-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as utilities so the stage is sized before the browser build compiles. */
.kbd-02 {
width: 100%;
min-height: 100svh;
position: relative;
}
```How this works
The wall is a shadow, not a border. A zero-blur shadow offset 6px down paints the cap's side. On press the face moves 4px and the shadow shrinks by the same 4px, so the bottom edge never moves:
shadow-[0_6px_0_var(--wall)] → group-active:translate-y-1
group-active:shadow-[0_2px_0_var(--wall)]Why a button wrapper. A focusable <kbd tabindex=0> never receives :active from the keyboard. A <button> does when Space is held, so the press is visible to keyboard users too.
Make it yours
- Deeper travel:
0_8px_0wall withgroup-active:translate-y-1.5and a0_2px_0pressed wall. - Accent caps: swap the token set (the Esc cap uses
kbd02-acc*). - Real-keyboard legends:
items-end justify-items-start pb-2.5moves a label to the bottom-left, as on the Shift cap.
Gotchas — read before shipping
- Holding Enter fires click but never sets
:active; only Space does — the hint says so. - Keep the shadow blur at 0 for the wall; blur reads as a floating card, not a key.
- Transition
translate, nottransform— Tailwind v4'stranslate-y-*sets the individualtranslateproperty.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 123+ | 17.5+ | 128+ | 123+ |
Tailwind v4's own sheet needs Chrome 111 / Safari 16.4 / Firefox 128 (cascade layers, @property, color-mix()); the light-dark() theme tokens raise the floor to Chrome 123 / Safari 17.5.