Mechanical keycap
Price: FreeFor small actions, with an optional shortcut printed on the key. It widens to fit its label.
Copy the CSS once and the markup wherever you need it. Set --c to change the color and --ink for the label; all the shading follows from those. It uses system fonts, so it needs nothing from us; set --btn-font to use your own. Covered by the Atomic Socket License.
React
<button type="button" className="keycap" style={{ "--c": "#e8622a", "--ink": "#fff5ec" } as React.CSSProperties}>
<span className="keycap-skirt">
<span className="keycap-top">
<span className="keycap-label">Export</span>
<span className="keycap-sub">Enter</span>
</span>
</span>
</button>HTML
<button type="button" class="keycap" style="--c:#e8622a;--ink:#fff5ec">
<span class="keycap-skirt">
<span class="keycap-top">
<span class="keycap-label">Export</span>
<span class="keycap-sub">Enter</span>
</span>
</span>
</button>CSS
/* Buttons by EELUMA: eeluma.com/library/buttons */
.keycap, .keycap *, .rocker, .rocker *, .arcade, .arcade * { box-sizing: border-box; }
/* Mechanical keycap: a small action, with an optional shortcut */
.keycap {
--c: #e7dfca; --ink: #3a362c;
--c-hi: color-mix(in oklab, var(--c), white 42%);
--c-side: color-mix(in oklab, var(--c), black 13%);
--c-front: color-mix(in oklab, var(--c), black 27%);
--c-wall: color-mix(in oklab, var(--c), black 50%);
display: inline-block; padding: 3px 3px 8px; border: 0; border-radius: 11px;
cursor: pointer; font: inherit; text-decoration: none; -webkit-tap-highlight-color: transparent;
background: #141516;
box-shadow: inset 0 2px 3px #000, 0 1px 0 rgba(255,255,255,.18), 0 0 0 1px rgba(0,0,0,.35);
}
.keycap-skirt {
display: block; padding: 3px 6px 9px; border-radius: 8px;
background:
linear-gradient(to top, var(--c-front) 0 9px, transparent 13px),
linear-gradient(90deg, var(--c-side), var(--c) 12%, var(--c) 88%, var(--c-side));
box-shadow: 0 5px 0 var(--c-wall), inset 0 1px 0 rgba(255,255,255,.55);
transition: transform 150ms cubic-bezier(.3,1.5,.5,1), box-shadow 150ms cubic-bezier(.3,1.5,.5,1);
}
.keycap-top {
display: flex; flex-direction: column; justify-content: space-between; gap: 7px;
min-width: 48px; min-height: 46px; padding: 6px 10px 6px 8px; border-radius: 6px; text-align: left;
color: var(--ink);
background: radial-gradient(120% 110% at 50% 36%, var(--c) 22%, var(--c-hi) 100%);
box-shadow: inset 0 1px 2px rgba(0,0,0,.12), inset 0 -1px 0 rgba(255,255,255,.55), 0 1px 2px rgba(0,0,0,.18);
}
.keycap-label { font: 500 13px/1 var(--btn-font, ui-monospace, "SF Mono", Menlo, Consolas, monospace); white-space: nowrap; }
.keycap-sub { font: 400 10px/1 var(--btn-font, ui-monospace, "SF Mono", Menlo, Consolas, monospace); opacity: .68; white-space: nowrap; align-self: flex-end; }
.keycap:hover .keycap-top { background: radial-gradient(120% 110% at 50% 36%, color-mix(in oklab, var(--c), white 10%) 22%, var(--c-hi) 100%); }
.keycap:active .keycap-skirt {
transform: translateY(4px);
box-shadow: 0 1px 0 var(--c-wall), inset 0 1px 0 rgba(255,255,255,.35);
transition-duration: 35ms; transition-timing-function: ease-out;
}
.keycap:focus-visible { outline: 2px solid var(--btn-focus, #2d6cdf); outline-offset: 3px; }
.keycap.wide .keycap-top { min-width: 210px; justify-content: center; align-items: center; }
@media (prefers-reduced-motion: reduce) { .keycap-skirt { transition: none; } }