GlassmorphismFree

Neu Button

Two 180 × 54 px buttons cut from the same material as their #e0e5ec background: a dark shadow bottom right, a white shadow top left, and the pressed state switches to inset shadows. Pure CSS, zero JavaScript.

CSSNeumorphismHover

Updated

Hover or click the scene to interact

This effect is free — the Glassmorphism category contains 47 effects total, including 6 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Résidence Les Tilleuls · Building B

Flat 12Mr and Mrs Lebrun · 3rd floor

Press Call to ring the flat

① Call keys of a video intercom — touch screen of a building's door panel

WhenThe visitor has picked flat 12 on the street panel: two large keys remain, Call and Hang up.
WhyThe relief echoes the metal buttons of older door panels, and the pressed state that stays visible confirms the call is going out without another screen. Two 180 × 54 px keys are easy to hit, even with gloves.
SettingsOriginal size and shadows. Both buttons side by side in .neu-btn-scene, which carries the #e0e5ec surface in the middle of a steel plate. On click, the scene sets .neu-btn--pressed and aria-pressed="true" on Call and updates a status line in role="status"; Hang up raises both keys again.

Espresso

40 ml · intensity 4/5

Brewing… 14 s

② Start and pause on a bean-to-cup coffee machine — the machine's touch screen

WhenThe user has started an espresso: the Start button stays down while it brews, Pause waits next to it.
WhyOn an appliance, a button has to look like a button. The hollow that stays visible says brewing is under way without reading the screen; a flat button would only change color.
SettingsButtons stacked as in the code (column, 16 px gap), to the right of a drink summary. Start begins with .neu-btn--pressed; a click on Pause moves the pressed state and empties the brewing gauge. Status label in #6d28d9 (5.6:1) rather than the original violet.

Disarmed

Doors and windows closed. Ready to arm.

4 zones · 2 detectors · garage included

③ Arming the alarm on the way out — wall keypad of a home alarm

WhenLeaving the house, the resident presses Arm on the hallway keypad; the light turns red and the button stays down.
WhyA pressed state that persists says “armed” at a glance, like a mechanical switch left down. Doubling it with text and a status light avoids relying on the shadow alone.
SettingsKeypad surface = .neu-btn-scene, on a beige wall: the relief holds because the surface keeps its own tint. Arm and Disarm are mutually exclusive; the light (green/red) and the “Armed — exit within 30 s” text are driven by the scene.

How it works

It all fits in three rules. .neu-btn is 180 × 54 px, border-radius: 16px, no border, with the same background as the surface (#e0e5ec) and two opposite drop shadows: 8px 8px 16px #b8bec7 (the shaded side) and -8px -8px 16px #ffffff (the lit side). That pair is what lifts the button out of the material: no outline, no gradient. On hover the shadows tighten to 6 px / 12 px and the text turns from #6b7280 to violet #8b5cf6; transition: all 0.2s ease smooths the change. :active and the .neu-btn--pressed class share one rule: both shadows become inset (inset 4px 4px 8px #b8bec7, inset -4px -4px 8px #ffffff) and the button looks carved in. The SVG icon (18 px, 2 px stroke) follows currentColor, so it turns violet with the text.

Neumorphism only works when the light shadow falls on the button's own tint: on a black background, -8px -8px 16px #ffffff becomes a white glow; on a white one it vanishes. That is why .neu-btn-scene declares its own surface (background: #e0e5ec, padding: 32px, border-radius: 24px). The 32 px hold the whole shadow (8 px offset + 16 px blur = 24 px), so the effect keeps its relief when pasted into any page, light or dark, without touching body. Measured in Chromium and Firefox: computed background rgb(224, 229, 236) on the container, no glow outside the surface.

The .neu-btn--pressed class is a state you set yourself: the CSS never changes it on its own. For a button that stays down (call in progress, alarm armed), add it on click and pair it with aria-pressed="true" — which is what the three scenes below do with a dozen lines of JavaScript that are not part of the effect.

Accessibility

  • Text contrast: #6b7280 on #e0e5ec measures 3.8:1 and the violet #8b5cf6 (hover, pressed) 3.3:1, for 15.2 px text at weight 600 — below the 4.5:1 threshold in both cases. Darken the label to #4b5563 (6.0:1) and the violet to #6d28d9 (5.6:1): the relief does not depend on them.
  • Button boundaries: the button only exists through its shadows. #b8bec7 on #e0e5ec is 1.5:1, far from the 3:1 required for components (WCAG 1.4.11), and the white shadow has no contrast at all. A low-vision user may only see floating text: add a 1px solid #9ca3af border if your audience needs it, or keep the icon and text clearly readable.
  • Pressed state: .neu-btn--pressed is purely visual. A screen reader announces “button” without saying whether it is pressed: set aria-pressed="true" / "false" together with the class. The information is also carried by color alone (violet) and the shadow: a status text next to it (“Calling…”) removes any doubt.
  • Keyboard: these are real <button> elements, reachable with Tab and activated with Enter or Space. The code defines no focus style: the browser's default ring shows (checked in Chromium and Firefox). To match the violet: .neu-btn:focus-visible { outline: 2px solid #8b5cf6; outline-offset: 3px }. Add type="button" if the button lives in a form, otherwise it submits it.
  • Motion: only a 0.2 s transition on shadows and color, triggered by the user. Nothing moves on its own; prefers-reduced-motion has nothing to switch off.

Browser compatibility

CSS only: multiple and inset box-shadow, border-radius, transition, flexbox with gap (Chrome 84, Firefox 63, Safari 14.1), inline SVG with stroke: currentColor. Zero JavaScript, zero dependencies. Identical rendering measured in Chromium, Firefox and WebKit (Playwright): surface background rgb(224, 229, 236), inset shadows on the pressed state. The 'Inter' font is not loaded by the effect: without it, the browser uses the system sans-serif.

Chrome 84+✓ Full
Firefox 63+✓ Full
Safari 14.1+✓ Full
Edge 84+✓ Full
Mobile iOS 14.5+✓ Full (:active shows while pressing)
Android Chrome 84+✓ Full

Without box-shadow (very old browsers), the buttons stay rounded gray rectangles, readable and clickable, with no relief. Before flex-container gap, the two buttons touch and the icon sticks to the text: add margins. On touch screens there is no hover: :active only lasts while the finger is down, hence the .neu-btn--pressed class for a state that persists.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="neu-btn-scene">
    <button class="neu-btn">
      <svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
      Continue
    </button>
    <button class="neu-btn neu-btn--pressed">
      <svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg>
      Done
    </button>
  </div>
CSS
.neu-btn-scene {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  background: #e0e5ec;
  padding: 32px;
  border-radius: 24px;
}

.neu-btn {
  width: 180px;
  height: 54px;
  border-radius: 16px;
  background: #e0e5ec;
  box-shadow: 8px 8px 16px #b8bec7, -8px -8px 16px #ffffff;
  border: none;
  font-family: 'Inter', sans-serif;
  font-size: 0.95rem;
  font-weight: 600;
  color: #6b7280;
  cursor: pointer;
  transition: all 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.neu-btn:hover {
  box-shadow: 6px 6px 12px #b8bec7, -6px -6px 12px #ffffff;
  color: #8b5cf6;
}

.neu-btn:active,
.neu-btn--pressed {
  box-shadow: inset 4px 4px 8px #b8bec7, inset -4px -4px 8px #ffffff;
  color: #8b5cf6;
}

.neu-btn svg {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
}
JavaScript (fx-0311)

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Material tint (CSS — #e0e5ec on .neu-btn and .neu-btn-scene) #e0e5ec, shadows #b8bec7 and #ffffff Change the three colors together: the background, a shadow about 15% darker and a lighter one. For a dark material: background #2a2d34, shadows #1d1f24 and #373b44. The button and its surface must always share the same tint, otherwise the relief reads as a smudge.
Depth (CSS — 8px 8px 16px at rest, 6px 6px 12px on hover) 8 px offset, 16 px blur Double offset and blur (12 px / 24 px) for stronger relief, and raise the padding of .neu-btn-scene to match (it must stay above offset + blur). Below 4 px / 8 px, the button melts into the surface.
Pressed state (CSS — .neu-btn--pressed class) inset 4px 4px 8px Set the class from JavaScript for a state that persists, together with aria-pressed. For a deeper hollow: inset 6px 6px 12px #b8bec7, inset -6px -6px 12px #ffffff.
Size (CSS — width: 180px; height: 54px; border-radius: 16px) 180 × 54 px A square appliance key (64 × 64 px, border-radius: 50%) keeps the same look. Keep at least 44 px of height for touch.
Accent color (CSS — color: #8b5cf6 on hover and pressed) #8b5cf6 (3.3:1 on the material) #6d28d9 reaches 5.6:1; an appliance green #166534 5.6:1. The icon follows, since it uses currentColor.

FAQ

Why do my buttons have a white glow on my dark site?

Because the light shadow -8px -8px 16px #ffffff is meant to fall on the button's own tint. The sold code therefore gives .neu-btn-scene its own #e0e5ec surface with 32 px of padding: keep that container around the buttons and the relief stays right on any background. If you remove the surface, place the buttons on a block of their own color.

How do I keep a button pressed after the click?

:active only lasts while pressing. Add the class from JavaScript: btn.addEventListener('click', () => { const on = btn.classList.toggle('neu-btn--pressed'); btn.setAttribute('aria-pressed', on); }). For a group where only one button stays down (Start / Pause), remove the class from the others first.

How is it different from Neu Toggle (fx-0313)?

Neu Button is an action: you press, it sinks in, the page decides what happens next. Neu Toggle is a two-state switch with a carved track and a knob that slides with a bounce, announced as role="switch". To turn a setting on or off, use the toggle; to start or stop something, the button.