Library / Buttons

Mechanical keycap

Price: Free

For small actions, with an optional shortcut printed on the key. It widens to fit its label.

See all kits

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; } }