Open demo

CSS code

HTML
<button type="button" class="td">Press me</button>
<button type="button" class="td td-green">Confirm</button>
<button type="button" class="td td-amber">Warning</button>
<button type="button" class="td td-white">Neutral</button>
<button type="button" class="td td-arcade">Go!</button>
CSS
/* 3D buttons: a solid box-shadow draws the ledge. Hover sinks the button 2px, pressing sinks it onto the ledge. */
.td{--td-face:#4f46e5;--td-ink:#fff;--td-edge:#3730a3;--td-shadow:0 6px 0 var(--td-edge);
  display:inline-block;border:0;border-radius:13px;padding:14px 30px;font:inherit;font-size:.92rem;font-weight:800;line-height:1.25;
  color:var(--td-ink);background:var(--td-face);box-shadow:var(--td-shadow);cursor:pointer;transition:transform .1s,box-shadow .1s}
.td:hover{transform:translateY(2px);--td-shadow:0 4px 0 var(--td-edge)}
.td:active{transform:translateY(6px);--td-shadow:0 0 0 var(--td-edge)}
.td:focus-visible{outline:2px solid #4f46e5;outline-offset:3px}
.td-green{--td-face:#15803d;--td-edge:#14532d}
.td-amber{--td-face:#f59e0b;--td-ink:#451a03;--td-edge:#b45309}
.td-white{--td-face:#fff;--td-ink:#1e293b;--td-edge:#cbd5e1}
/* round arcade button: deeper ledge plus a soft drop shadow, no hover step */
.td-arcade{--td-face:#dc2626;--td-edge:#7f1d1d;--td-shadow:0 8px 0 var(--td-edge),0 12px 20px rgba(0,0,0,.25);
  width:88px;height:88px;border-radius:50%;font-size:.8rem;text-transform:uppercase;letter-spacing:.05em}
.td-arcade:hover{transform:none;--td-shadow:0 8px 0 var(--td-edge),0 12px 20px rgba(0,0,0,.25)}
.td-arcade:active{transform:translateY(8px);--td-shadow:0 0 0 var(--td-edge),0 2px 6px rgba(0,0,0,.25)}
@media (prefers-reduced-motion:reduce){.td{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<button type="button" class="btn btn-3d">Press me</button>
<button type="button" class="btn btn-3d btn-3d-green">Confirm</button>
<button type="button" class="btn btn-3d btn-3d-amber">Warning</button>
<button type="button" class="btn btn-3d btn-3d-white">Neutral</button>
<button type="button" class="btn btn-3d btn-3d-arcade">Go!</button>
CSS
/* Bootstrap .btn with every state colour pinned to one face colour; the ledge is --td-shadow, reused as the focus shadow. */
.btn-3d{--td-face:#4f46e5;--td-ink:#fff;--td-edge:#3730a3;--td-shadow:0 6px 0 var(--td-edge);
  --bs-btn-padding-x:30px;--bs-btn-padding-y:14px;--bs-btn-font-size:.92rem;--bs-btn-font-weight:800;--bs-btn-line-height:1.25;
  --bs-btn-border-width:0;--bs-btn-border-radius:13px;
  --bs-btn-color:var(--td-ink);--bs-btn-hover-color:var(--td-ink);--bs-btn-active-color:var(--td-ink);
  --bs-btn-bg:var(--td-face);--bs-btn-hover-bg:var(--td-face);--bs-btn-active-bg:var(--td-face);
  --bs-btn-focus-box-shadow:var(--td-shadow);
  box-shadow:var(--td-shadow);transition:transform .1s,box-shadow .1s}
.btn-3d:hover{transform:translateY(2px);--td-shadow:0 4px 0 var(--td-edge)}
.btn-3d:active{transform:translateY(6px);--td-shadow:0 0 0 var(--td-edge)}
.btn-3d:focus-visible{outline:2px solid #4f46e5;outline-offset:3px}
.btn-3d-green{--td-face:#15803d;--td-edge:#14532d}
.btn-3d-amber{--td-face:#f59e0b;--td-ink:#451a03;--td-edge:#b45309}
.btn-3d-white{--td-face:#fff;--td-ink:#1e293b;--td-edge:#cbd5e1}
.btn-3d-arcade{--td-face:#dc2626;--td-edge:#7f1d1d;--td-shadow:0 8px 0 var(--td-edge),0 12px 20px rgba(0,0,0,.25);
  --bs-btn-font-size:.8rem;width:88px;height:88px;border-radius:50%;text-transform:uppercase;letter-spacing:.05em}
.btn-3d-arcade:hover{transform:none;--td-shadow:0 8px 0 var(--td-edge),0 12px 20px rgba(0,0,0,.25)}
.btn-3d-arcade:active{transform:translateY(8px);--td-shadow:0 0 0 var(--td-edge),0 2px 6px rgba(0,0,0,.25)}
@media (prefers-reduced-motion:reduce){.btn-3d{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<button type="button" class="cursor-pointer rounded-[13px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-extrabold transition-[translate,box-shadow] duration-100 hover:translate-y-0.5 active:translate-y-1.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5] motion-reduce:transition-none bg-[#4f46e5] text-white shadow-[0_6px_0_#3730a3] hover:shadow-[0_4px_0_#3730a3] active:shadow-[0_0_0_#3730a3]">Press me</button>
<button type="button" class="cursor-pointer rounded-[13px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-extrabold transition-[translate,box-shadow] duration-100 hover:translate-y-0.5 active:translate-y-1.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5] motion-reduce:transition-none bg-[#15803d] text-white shadow-[0_6px_0_#14532d] hover:shadow-[0_4px_0_#14532d] active:shadow-[0_0_0_#14532d]">Confirm</button>
<button type="button" class="cursor-pointer rounded-[13px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-extrabold transition-[translate,box-shadow] duration-100 hover:translate-y-0.5 active:translate-y-1.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5] motion-reduce:transition-none bg-[#f59e0b] text-[#451a03] shadow-[0_6px_0_#b45309] hover:shadow-[0_4px_0_#b45309] active:shadow-[0_0_0_#b45309]">Warning</button>
<button type="button" class="cursor-pointer rounded-[13px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-extrabold transition-[translate,box-shadow] duration-100 hover:translate-y-0.5 active:translate-y-1.5 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5] motion-reduce:transition-none bg-white text-[#1e293b] shadow-[0_6px_0_#cbd5e1] hover:shadow-[0_4px_0_#cbd5e1] active:shadow-[0_0_0_#cbd5e1]">Neutral</button>
<button type="button" class="size-[88px] cursor-pointer rounded-full bg-[#dc2626] px-[30px] py-3.5 text-[.8rem] leading-[1.25] font-extrabold tracking-[.05em] text-white uppercase shadow-[0_8px_0_#7f1d1d,0_12px_20px_rgba(0,0,0,.25)] transition-[translate,box-shadow] duration-100 active:translate-y-2 active:shadow-[0_0_0_#7f1d1d,0_2px_6px_rgba(0,0,0,.25)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Go!</button>

About this set

Chunky buttons that look like physical keys. A solid, unblurred box-shadow in a darker shade of the face colour draws a 6 pixel ledge under each button. On hover the button sinks 2 pixels and the ledge shrinks to match, and while it is pressed it drops all the way onto the ledge, which gives a clear click feel without images or extra markup. The set has four rectangular variants, indigo Press me, green Confirm, amber Warning and a white Neutral button, plus a round red arcade button with a deeper 8 pixel ledge and a soft drop shadow that only reacts to presses. Use them in playful interfaces, games, onboarding flows and kids’ products, where a button that visibly moves suits the tone. Each variant is a set of three custom properties, the face, label and edge colours, so a new colour takes one line. The green face is a deep #15803d so its white label reaches 4.5:1. The Bootstrap version pins every .btn state colour to the face and reuses the ledge as its focus box-shadow, so focus never removes the 3D edge.

What’s included

  • Solid box-shadow ledge that shrinks on hover and disappears on press
  • Four rectangular colours plus a round arcade button
  • One custom property each for face, label and ledge colour
  • Ledge kept on keyboard focus in the Bootstrap version
  • Transitions removed under prefers-reduced-motion

Accessibility

The buttons are native button elements with visible labels, reachable with Tab and activated with Enter or Space; the arcade button's label is the word Go in the markup and is only uppercased by CSS. Focus shows a 2px indigo outline offset from the button. With prefers-reduced-motion set, the press and hover changes happen instantly instead of animating.

Customise it

Set --td-face, --td-ink and --td-edge on a modifier class (.td-green and so on) in plain CSS, or the same variables on .btn-3d-* in Bootstrap. In Tailwind change the bg-, text- and the three shadow-[0_6px_0_...] utilities per button.