20 CSS Kbd Keys04 / 20
Flat Minimalist Keycap for Body Text
No depth, no shadow: a tinted rounded rectangle with a hairline border, sized to disappear into dense body copy. This is the treatment most documentation sites actually ship, because a physical cap repeated forty times down a reference page becomes visual noise. Set beside demo 01 so you can see exactly what you trade away and what you get back.
Published
The code
<div class="kbd-04">
<div class="kbd-04__stage">
<article class="kbd-04__doc">
<h2 class="kbd-04__h">Selection and navigation</h2>
<p class="kbd-04__p">Hold <kbd class="kbd-04__key">Shift</kbd> and use the arrow keys to grow the selection by one character, or add <kbd class="kbd-04__key">Alt</kbd> to grow it by one word. <kbd class="kbd-04__key">Home</kbd> and <kbd class="kbd-04__key">End</kbd> jump to the ends of the visual line; add <kbd class="kbd-04__key">Ctrl</kbd> to jump to the ends of the document instead. Numbered tabs respond to <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">1</kbd> through <kbd class="kbd-04__key">9</kbd>, and <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">0</kbd> returns to the last one you looked at.</p>
<p class="kbd-04__p">Twelve keys in two paragraphs, and the page still reads as prose. That is the argument for the flat treatment.</p>
<div class="kbd-04__compare">
<div class="kbd-04__col">
<p class="kbd-04__collabel">Flat · this demo</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key">Ctrl</kbd> <kbd class="kbd-04__key">K</kbd> to search.</p>
<p class="kbd-04__note">Reference prose, changelogs, dense help pages.</p>
</div>
<div class="kbd-04__col">
<p class="kbd-04__collabel">Raised · demo 01</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key kbd-04__key--raised">Ctrl</kbd> <kbd class="kbd-04__key kbd-04__key--raised">K</kbd> to search.</p>
<p class="kbd-04__note">Tips, callouts, first-run hints — one shortcut at a time.</p>
</div>
</div>
</article>
<div class="kbd-04__theme">
<input class="kbd-04__themebox" type="checkbox" id="kbd-04-theme">
<label class="kbd-04__themelabel" for="kbd-04-theme">
<span class="kbd-04__themedot" aria-hidden="true"></span>
<span class="kbd-04__themebase">Light</span><span class="kbd-04__themeflip">Dark</span>
</label>
</div>
</div>
</div><div class="kbd-04">
<div class="kbd-04__stage">
<article class="kbd-04__doc">
<h2 class="kbd-04__h">Selection and navigation</h2>
<p class="kbd-04__p">Hold <kbd class="kbd-04__key">Shift</kbd> and use the arrow keys to grow the selection by one character, or add <kbd class="kbd-04__key">Alt</kbd> to grow it by one word. <kbd class="kbd-04__key">Home</kbd> and <kbd class="kbd-04__key">End</kbd> jump to the ends of the visual line; add <kbd class="kbd-04__key">Ctrl</kbd> to jump to the ends of the document instead. Numbered tabs respond to <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">1</kbd> through <kbd class="kbd-04__key">9</kbd>, and <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">0</kbd> returns to the last one you looked at.</p>
<p class="kbd-04__p">Twelve keys in two paragraphs, and the page still reads as prose. That is the argument for the flat treatment.</p>
<div class="kbd-04__compare">
<div class="kbd-04__col">
<p class="kbd-04__collabel">Flat · this demo</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key">Ctrl</kbd> <kbd class="kbd-04__key">K</kbd> to search.</p>
<p class="kbd-04__note">Reference prose, changelogs, dense help pages.</p>
</div>
<div class="kbd-04__col">
<p class="kbd-04__collabel">Raised · demo 01</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key kbd-04__key--raised">Ctrl</kbd> <kbd class="kbd-04__key kbd-04__key--raised">K</kbd> to search.</p>
<p class="kbd-04__note">Tips, callouts, first-run hints — one shortcut at a time.</p>
</div>
</div>
</article>
<div class="kbd-04__theme">
<input class="kbd-04__themebox" type="checkbox" id="kbd-04-theme">
<label class="kbd-04__themelabel" for="kbd-04-theme">
<span class="kbd-04__themedot" aria-hidden="true"></span>
<span class="kbd-04__themebase">Light</span><span class="kbd-04__themeflip">Dark</span>
</label>
</div>
</div>
</div>.kbd-04 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
--radius: 4px;
--key-size: 0.83em;
--font-key: ui-sans-serif, system-ui, "Helvetica Neue", sans-serif;
--font-body: "Iowan Old Style", Palatino, Georgia, serif;
background: var(--page);
color: var(--ink);
padding: 40px 20px;
}
.kbd-04__stage {
display: grid;
place-items: center;
min-height: calc(100vh - 80px);
}
.kbd-04__doc {
inline-size: 100%;
max-inline-size: 34rem;
min-inline-size: 0;
}
.kbd-04__h {
margin: 0 0 16px;
font: 400 clamp(23px, 5vw, 30px)/1.2 var(--font-body);
letter-spacing: -0.01em;
}
.kbd-04__p {
margin: 0 0 16px;
font: 400 clamp(15px, 3.3vw, 16.5px)/1.8 var(--font-body);
text-wrap: pretty;
}
.kbd-04__key {
display: inline-block;
padding: 0.2em 0.45em;
min-inline-size: 1.15em;
box-sizing: content-box;
font-family: var(--font-key);
font-size: var(--key-size);
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
color: var(--cap-ink);
background: var(--cap);
border: 1px solid var(--cap-border);
border-radius: var(--radius);
vertical-align: 0.08em;
white-space: nowrap;
}
@supports (background: color-mix(in oklab, #000 10%, #fff)) {
.kbd-04__key {
background: color-mix(in oklab, var(--ink) 9%, var(--page));
border-color: color-mix(in oklab, var(--ink) 18%, var(--page));
}
}
.kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
.kbd-04__compare {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--rule);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 22px;
}
.kbd-04__col {
min-inline-size: 0;
}
.kbd-04__collabel {
margin: 0 0 8px;
font: 600 9.5px/1 var(--font-key);
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
.kbd-04__example {
margin: 0 0 8px;
font: 400 15px/1.7 var(--font-body);
}
.kbd-04__note {
margin: 0;
font: 400 12px/1.5 var(--font-key);
color: var(--muted);
text-wrap: pretty;
}
.kbd-04[data-theme="dark"],
[data-theme="dark"] .kbd-04 {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
/* In-demo theme switch. State is one checkbox inside this demo, so the toggle
themes THIS demo only — no root attribute, no script. */
.kbd-04__theme {
position: absolute;
inset-block-start: 14px;
inset-inline-end: 16px;
z-index: 5;
display: flex;
}
/* the pill owns the top-right corner, so the stage reserves that band */
.kbd-04__stage {
box-sizing: border-box;
padding-block-start: 46px;
}
.kbd-04__themebox {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
pointer-events: none;
}
.kbd-04__themelabel {
display: inline-flex;
align-items: center;
gap: 9px;
min-block-size: 44px;
padding: 0 16px;
box-sizing: border-box;
font-family: var(--font-key, ui-sans-serif, system-ui, sans-serif);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
line-height: 1;
color: var(--ink, var(--cap-ink, currentColor));
background: transparent;
border: 1px solid currentColor;
border-radius: 999px;
cursor: pointer;
user-select: none;
}
.kbd-04__themedot {
inline-size: 12px;
block-size: 12px;
flex: none;
border-radius: 50%;
background: currentColor;
}
.kbd-04__themebox:focus-visible + .kbd-04__themelabel {
outline: 3px solid var(--accent, var(--cap-ink, currentColor));
outline-offset: 2px;
}
@supports (border-color: color-mix(in oklab, currentColor 50%, transparent)) {
.kbd-04__themelabel {
border-color: color-mix(in oklab, currentColor 65%, transparent);
}
}
.kbd-04__themeflip {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
/* The flip-back rule: on the opposite OS preference the unchecked state is the
flipped palette, and checking the box returns to the demo's own default. */
@media (prefers-color-scheme: dark) {
.kbd-04:not(:has(.kbd-04__themebox:checked)) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themebase {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
}.kbd-04 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
--radius: 4px;
--key-size: 0.83em;
--font-key: ui-sans-serif, system-ui, "Helvetica Neue", sans-serif;
--font-body: "Iowan Old Style", Palatino, Georgia, serif;
background: var(--page);
color: var(--ink);
padding: 40px 20px;
}
.kbd-04__stage {
display: grid;
place-items: center;
min-height: calc(100vh - 80px);
}
.kbd-04__doc {
inline-size: 100%;
max-inline-size: 34rem;
min-inline-size: 0;
}
.kbd-04__h {
margin: 0 0 16px;
font: 400 clamp(23px, 5vw, 30px)/1.2 var(--font-body);
letter-spacing: -0.01em;
}
.kbd-04__p {
margin: 0 0 16px;
font: 400 clamp(15px, 3.3vw, 16.5px)/1.8 var(--font-body);
text-wrap: pretty;
}
.kbd-04__key {
display: inline-block;
padding: 0.2em 0.45em;
min-inline-size: 1.15em;
box-sizing: content-box;
font-family: var(--font-key);
font-size: var(--key-size);
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
color: var(--cap-ink);
background: var(--cap);
border: 1px solid var(--cap-border);
border-radius: var(--radius);
vertical-align: 0.08em;
white-space: nowrap;
}
@supports (background: color-mix(in oklab, #000 10%, #fff)) {
.kbd-04__key {
background: color-mix(in oklab, var(--ink) 9%, var(--page));
border-color: color-mix(in oklab, var(--ink) 18%, var(--page));
}
}
.kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
.kbd-04__compare {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--rule);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 22px;
}
.kbd-04__col {
min-inline-size: 0;
}
.kbd-04__collabel {
margin: 0 0 8px;
font: 600 9.5px/1 var(--font-key);
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
.kbd-04__example {
margin: 0 0 8px;
font: 400 15px/1.7 var(--font-body);
}
.kbd-04__note {
margin: 0;
font: 400 12px/1.5 var(--font-key);
color: var(--muted);
text-wrap: pretty;
}
.kbd-04[data-theme="dark"],
[data-theme="dark"] .kbd-04 {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
/* In-demo theme switch. State is one checkbox inside this demo, so the toggle
themes THIS demo only — no root attribute, no script. */
.kbd-04__theme {
position: absolute;
inset-block-start: 14px;
inset-inline-end: 16px;
z-index: 5;
display: flex;
}
/* the pill owns the top-right corner, so the stage reserves that band */
.kbd-04__stage {
box-sizing: border-box;
padding-block-start: 46px;
}
.kbd-04__themebox {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
pointer-events: none;
}
.kbd-04__themelabel {
display: inline-flex;
align-items: center;
gap: 9px;
min-block-size: 44px;
padding: 0 16px;
box-sizing: border-box;
font-family: var(--font-key, ui-sans-serif, system-ui, sans-serif);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
line-height: 1;
color: var(--ink, var(--cap-ink, currentColor));
background: transparent;
border: 1px solid currentColor;
border-radius: 999px;
cursor: pointer;
user-select: none;
}
.kbd-04__themedot {
inline-size: 12px;
block-size: 12px;
flex: none;
border-radius: 50%;
background: currentColor;
}
.kbd-04__themebox:focus-visible + .kbd-04__themelabel {
outline: 3px solid var(--accent, var(--cap-ink, currentColor));
outline-offset: 2px;
}
@supports (border-color: color-mix(in oklab, currentColor 50%, transparent)) {
.kbd-04__themelabel {
border-color: color-mix(in oklab, currentColor 65%, transparent);
}
}
.kbd-04__themeflip {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
/* The flip-back rule: on the opposite OS preference the unchecked state is the
flipped palette, and checking the box returns to the demo's own default. */
@media (prefers-color-scheme: dark) {
.kbd-04:not(:has(.kbd-04__themebox:checked)) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themebase {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
}Here's a working CSS Kbd Key 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: Flat Minimalist Keycap for Body Text
Source: https://codefronts.com/snippets/css-kbd-keys/flat-minimalist-keycap-for-body-text/
No depth, no shadow: a tinted rounded rectangle with a hairline border, sized to disappear into dense body copy. This is the treatment most documentation sites actually ship, because a physical cap repeated forty times down a reference page becomes visual noise. Set beside demo 01 so you can see exactly what you trade away and what you get back.
## HTML
```html
<div class="kbd-04">
<div class="kbd-04__stage">
<article class="kbd-04__doc">
<h2 class="kbd-04__h">Selection and navigation</h2>
<p class="kbd-04__p">Hold <kbd class="kbd-04__key">Shift</kbd> and use the arrow keys to grow the selection by one character, or add <kbd class="kbd-04__key">Alt</kbd> to grow it by one word. <kbd class="kbd-04__key">Home</kbd> and <kbd class="kbd-04__key">End</kbd> jump to the ends of the visual line; add <kbd class="kbd-04__key">Ctrl</kbd> to jump to the ends of the document instead. Numbered tabs respond to <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">1</kbd> through <kbd class="kbd-04__key">9</kbd>, and <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">0</kbd> returns to the last one you looked at.</p>
<p class="kbd-04__p">Twelve keys in two paragraphs, and the page still reads as prose. That is the argument for the flat treatment.</p>
<div class="kbd-04__compare">
<div class="kbd-04__col">
<p class="kbd-04__collabel">Flat · this demo</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key">Ctrl</kbd> <kbd class="kbd-04__key">K</kbd> to search.</p>
<p class="kbd-04__note">Reference prose, changelogs, dense help pages.</p>
</div>
<div class="kbd-04__col">
<p class="kbd-04__collabel">Raised · demo 01</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key kbd-04__key--raised">Ctrl</kbd> <kbd class="kbd-04__key kbd-04__key--raised">K</kbd> to search.</p>
<p class="kbd-04__note">Tips, callouts, first-run hints — one shortcut at a time.</p>
</div>
</div>
</article>
<div class="kbd-04__theme">
<input class="kbd-04__themebox" type="checkbox" id="kbd-04-theme">
<label class="kbd-04__themelabel" for="kbd-04-theme">
<span class="kbd-04__themedot" aria-hidden="true"></span>
<span class="kbd-04__themebase">Light</span><span class="kbd-04__themeflip">Dark</span>
</label>
</div>
</div>
</div>
```
## CSS
```css
.kbd-04 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
--radius: 4px;
--key-size: 0.83em;
--font-key: ui-sans-serif, system-ui, "Helvetica Neue", sans-serif;
--font-body: "Iowan Old Style", Palatino, Georgia, serif;
background: var(--page);
color: var(--ink);
padding: 40px 20px;
}
.kbd-04__stage {
display: grid;
place-items: center;
min-height: calc(100vh - 80px);
}
.kbd-04__doc {
inline-size: 100%;
max-inline-size: 34rem;
min-inline-size: 0;
}
.kbd-04__h {
margin: 0 0 16px;
font: 400 clamp(23px, 5vw, 30px)/1.2 var(--font-body);
letter-spacing: -0.01em;
}
.kbd-04__p {
margin: 0 0 16px;
font: 400 clamp(15px, 3.3vw, 16.5px)/1.8 var(--font-body);
text-wrap: pretty;
}
.kbd-04__key {
display: inline-block;
padding: 0.2em 0.45em;
min-inline-size: 1.15em;
box-sizing: content-box;
font-family: var(--font-key);
font-size: var(--key-size);
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
color: var(--cap-ink);
background: var(--cap);
border: 1px solid var(--cap-border);
border-radius: var(--radius);
vertical-align: 0.08em;
white-space: nowrap;
}
@supports (background: color-mix(in oklab, #000 10%, #fff)) {
.kbd-04__key {
background: color-mix(in oklab, var(--ink) 9%, var(--page));
border-color: color-mix(in oklab, var(--ink) 18%, var(--page));
}
}
.kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
.kbd-04__compare {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--rule);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 22px;
}
.kbd-04__col {
min-inline-size: 0;
}
.kbd-04__collabel {
margin: 0 0 8px;
font: 600 9.5px/1 var(--font-key);
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
.kbd-04__example {
margin: 0 0 8px;
font: 400 15px/1.7 var(--font-body);
}
.kbd-04__note {
margin: 0;
font: 400 12px/1.5 var(--font-key);
color: var(--muted);
text-wrap: pretty;
}
.kbd-04[data-theme="dark"],
[data-theme="dark"] .kbd-04 {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
/* In-demo theme switch. State is one checkbox inside this demo, so the toggle
themes THIS demo only — no root attribute, no script. */
.kbd-04__theme {
position: absolute;
inset-block-start: 14px;
inset-inline-end: 16px;
z-index: 5;
display: flex;
}
/* the pill owns the top-right corner, so the stage reserves that band */
.kbd-04__stage {
box-sizing: border-box;
padding-block-start: 46px;
}
.kbd-04__themebox {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
pointer-events: none;
}
.kbd-04__themelabel {
display: inline-flex;
align-items: center;
gap: 9px;
min-block-size: 44px;
padding: 0 16px;
box-sizing: border-box;
font-family: var(--font-key, ui-sans-serif, system-ui, sans-serif);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
line-height: 1;
color: var(--ink, var(--cap-ink, currentColor));
background: transparent;
border: 1px solid currentColor;
border-radius: 999px;
cursor: pointer;
user-select: none;
}
.kbd-04__themedot {
inline-size: 12px;
block-size: 12px;
flex: none;
border-radius: 50%;
background: currentColor;
}
.kbd-04__themebox:focus-visible + .kbd-04__themelabel {
outline: 3px solid var(--accent, var(--cap-ink, currentColor));
outline-offset: 2px;
}
@supports (border-color: color-mix(in oklab, currentColor 50%, transparent)) {
.kbd-04__themelabel {
border-color: color-mix(in oklab, currentColor 65%, transparent);
}
}
.kbd-04__themeflip {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
/* The flip-back rule: on the opposite OS preference the unchecked state is the
flipped palette, and checking the box returns to the demo's own default. */
@media (prefers-color-scheme: dark) {
.kbd-04:not(:has(.kbd-04__themebox:checked)) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themebase {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
}
```Here's a working CSS Kbd Key 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: Flat Minimalist Keycap for Body Text
Source: https://codefronts.com/snippets/css-kbd-keys/flat-minimalist-keycap-for-body-text/
No depth, no shadow: a tinted rounded rectangle with a hairline border, sized to disappear into dense body copy. This is the treatment most documentation sites actually ship, because a physical cap repeated forty times down a reference page becomes visual noise. Set beside demo 01 so you can see exactly what you trade away and what you get back.
## HTML
```html
<div class="kbd-04">
<div class="kbd-04__stage">
<article class="kbd-04__doc">
<h2 class="kbd-04__h">Selection and navigation</h2>
<p class="kbd-04__p">Hold <kbd class="kbd-04__key">Shift</kbd> and use the arrow keys to grow the selection by one character, or add <kbd class="kbd-04__key">Alt</kbd> to grow it by one word. <kbd class="kbd-04__key">Home</kbd> and <kbd class="kbd-04__key">End</kbd> jump to the ends of the visual line; add <kbd class="kbd-04__key">Ctrl</kbd> to jump to the ends of the document instead. Numbered tabs respond to <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">1</kbd> through <kbd class="kbd-04__key">9</kbd>, and <kbd class="kbd-04__key">Ctrl</kbd> plus <kbd class="kbd-04__key">0</kbd> returns to the last one you looked at.</p>
<p class="kbd-04__p">Twelve keys in two paragraphs, and the page still reads as prose. That is the argument for the flat treatment.</p>
<div class="kbd-04__compare">
<div class="kbd-04__col">
<p class="kbd-04__collabel">Flat · this demo</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key">Ctrl</kbd> <kbd class="kbd-04__key">K</kbd> to search.</p>
<p class="kbd-04__note">Reference prose, changelogs, dense help pages.</p>
</div>
<div class="kbd-04__col">
<p class="kbd-04__collabel">Raised · demo 01</p>
<p class="kbd-04__example">Press <kbd class="kbd-04__key kbd-04__key--raised">Ctrl</kbd> <kbd class="kbd-04__key kbd-04__key--raised">K</kbd> to search.</p>
<p class="kbd-04__note">Tips, callouts, first-run hints — one shortcut at a time.</p>
</div>
</div>
</article>
<div class="kbd-04__theme">
<input class="kbd-04__themebox" type="checkbox" id="kbd-04-theme">
<label class="kbd-04__themelabel" for="kbd-04-theme">
<span class="kbd-04__themedot" aria-hidden="true"></span>
<span class="kbd-04__themebase">Light</span><span class="kbd-04__themeflip">Dark</span>
</label>
</div>
</div>
</div>
```
## CSS
```css
.kbd-04 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
--radius: 4px;
--key-size: 0.83em;
--font-key: ui-sans-serif, system-ui, "Helvetica Neue", sans-serif;
--font-body: "Iowan Old Style", Palatino, Georgia, serif;
background: var(--page);
color: var(--ink);
padding: 40px 20px;
}
.kbd-04__stage {
display: grid;
place-items: center;
min-height: calc(100vh - 80px);
}
.kbd-04__doc {
inline-size: 100%;
max-inline-size: 34rem;
min-inline-size: 0;
}
.kbd-04__h {
margin: 0 0 16px;
font: 400 clamp(23px, 5vw, 30px)/1.2 var(--font-body);
letter-spacing: -0.01em;
}
.kbd-04__p {
margin: 0 0 16px;
font: 400 clamp(15px, 3.3vw, 16.5px)/1.8 var(--font-body);
text-wrap: pretty;
}
.kbd-04__key {
display: inline-block;
padding: 0.2em 0.45em;
min-inline-size: 1.15em;
box-sizing: content-box;
font-family: var(--font-key);
font-size: var(--key-size);
font-weight: 500;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
color: var(--cap-ink);
background: var(--cap);
border: 1px solid var(--cap-border);
border-radius: var(--radius);
vertical-align: 0.08em;
white-space: nowrap;
}
@supports (background: color-mix(in oklab, #000 10%, #fff)) {
.kbd-04__key {
background: color-mix(in oklab, var(--ink) 9%, var(--page));
border-color: color-mix(in oklab, var(--ink) 18%, var(--page));
}
}
.kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
.kbd-04__compare {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--rule);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 22px;
}
.kbd-04__col {
min-inline-size: 0;
}
.kbd-04__collabel {
margin: 0 0 8px;
font: 600 9.5px/1 var(--font-key);
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
.kbd-04__example {
margin: 0 0 8px;
font: 400 15px/1.7 var(--font-body);
}
.kbd-04__note {
margin: 0;
font: 400 12px/1.5 var(--font-key);
color: var(--muted);
text-wrap: pretty;
}
.kbd-04[data-theme="dark"],
[data-theme="dark"] .kbd-04 {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
/* In-demo theme switch. State is one checkbox inside this demo, so the toggle
themes THIS demo only — no root attribute, no script. */
.kbd-04__theme {
position: absolute;
inset-block-start: 14px;
inset-inline-end: 16px;
z-index: 5;
display: flex;
}
/* the pill owns the top-right corner, so the stage reserves that band */
.kbd-04__stage {
box-sizing: border-box;
padding-block-start: 46px;
}
.kbd-04__themebox {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
pointer-events: none;
}
.kbd-04__themelabel {
display: inline-flex;
align-items: center;
gap: 9px;
min-block-size: 44px;
padding: 0 16px;
box-sizing: border-box;
font-family: var(--font-key, ui-sans-serif, system-ui, sans-serif);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
line-height: 1;
color: var(--ink, var(--cap-ink, currentColor));
background: transparent;
border: 1px solid currentColor;
border-radius: 999px;
cursor: pointer;
user-select: none;
}
.kbd-04__themedot {
inline-size: 12px;
block-size: 12px;
flex: none;
border-radius: 50%;
background: currentColor;
}
.kbd-04__themebox:focus-visible + .kbd-04__themelabel {
outline: 3px solid var(--accent, var(--cap-ink, currentColor));
outline-offset: 2px;
}
@supports (border-color: color-mix(in oklab, currentColor 50%, transparent)) {
.kbd-04__themelabel {
border-color: color-mix(in oklab, currentColor 65%, transparent);
}
}
.kbd-04__themeflip {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: none;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
/* The flip-back rule: on the opposite OS preference the unchecked state is the
flipped palette, and checking the box returns to the demo's own default. */
@media (prefers-color-scheme: dark) {
.kbd-04:not(:has(.kbd-04__themebox:checked)) {
--page: #121212;
--ink: #ececea;
--muted: #9a9a95;
--rule: #2a2a28;
--cap: #262624;
--cap-border: #3d3d39;
--cap-ink: #f0f0ec;
}
.kbd-04:has(.kbd-04__themebox:checked) {
--page: #fbfbf9;
--ink: #1b1b1a;
--muted: #6f6f6a;
--rule: #e4e4de;
--cap: #efefe9;
--cap-border: #d8d8d0;
--cap-ink: #26261f;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themebase {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__themeflip {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themebase {
display: inline;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__themeflip {
display: none;
}
.kbd-04:not(:has(.kbd-04__themebox:checked)) .kbd-04__key--raised {
background: #2f2f2c;
border-color: #4a4a45;
box-shadow: 0 1px 0 #0c0c0b;
}
.kbd-04:has(.kbd-04__themebox:checked) .kbd-04__key--raised {
background: #ffffff;
border-color: #cfcfc6;
box-shadow: 0 1px 0 #bdbdb2;
}
}
```How this works
Depth is a cost, not a feature. A raised cap draws the eye, which is exactly right once in an onboarding hint and exactly wrong forty times down a keyboard reference. Removing the shadow drops the key back into the reading plane, and the hairline border alone is enough to mark it as an object rather than emphasised text.
The tint is derived, not picked. The cap face is a small mix of the ink colour into the page background, so the key stays keyed to the palette on light and dark alike. A plain hex fallback is declared first and the color-mix() version follows behind @supports, so older engines get a hand-tuned tint instead of nothing.
Reach for flat when keys repeat. Use demo 01's raised cap when a shortcut is the point of the sentence: a tip, a callout, a first-run hint. Use this one in reference prose, changelogs, and dense help articles, where the shortcut is one fact among many and should not out-rank the sentence around it.
Digits are the hidden trap. Number keys in a proportional face have inconsistent widths, so 1 and 0 render as visibly different sizes. font-variant-numeric: tabular-nums plus a min-inline-size in em keeps every single-character cap the same width.
Make it yours
- Adjust the mix percentage in the cap background for a stronger or fainter tint.
- Set
--radiusto3pxfor a more technical read, or999pxfor pills. - Drop the border entirely and rely on the tint alone for the softest possible key.
- Change
--key-sizeto0.78emfor very dense reference pages. - Switch
--font-keyto a monospace stack to signal literal input. - Add
letter-spacing: 0.02emto the label if your sans face is tightly set.
Gotchas — read before shipping
- Without
tabular-numsand a minimum width, single-digit caps render at different widths and the row looks ragged. - A tint built only with
color-mix()and no fallback leaves the cap fully transparent on engines that lack it. - Flat keys with too little contrast against the page read as highlighted text rather than as keys — keep at least 3:1 between cap and page.
- Reusing this treatment for an interactive control removes the affordance; flat caps should be labels only.
- Hiding the switch's checkbox with
display:nonetakes it out of the tab order, so the toggle stops working for keyboard users — clip it instead.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
color-mix() gates the derived tint and sits behind @supports with a hex fallback declared first. Everything else in the demo works back to 2017. The versions above are set by :has(), which drives the in-demo theme switch; the demo's own key technique works back to Chrome 111, Safari 16.2 and Firefox 113. Without :has() the demo still follows the OS preference and only the switch stops responding.