Browse by section

Web Design 日本語

Neumorphism in UI Design, and How to Keep It Accessible

Neumorphism renders depth using nothing but two shadows on a surface the same colour as its background. In CSS it is two box-shadow declarations.

The conclusion up front, though: do not rely on neumorphism alone to define the boundary of an interactive control. WCAG 2.2 success criterion 1.4.11 (Non-text Contrast) requires a contrast ratio of at least 3:1 for the visual information that identifies a UI component, and a same-colour surface with a soft shadow structurally cannot meet it.

That does not make the technique worthless. It works fine under one condition: treat it as decoration and provide the affordance separately. This article covers how to build it and how to keep it accessible.

Sponsored

What neumorphism is

The name spread from Dribbble concepts in late 2019, describing a style that combines skeuomorphic depth with flat-design simplicity.

Style Characteristic Example
Skeuomorphism Imitates real materials and textures iOS 6 and earlier
Flat design Simplifies to shapes and colour Material Design
Neumorphism Same-colour surface pushed out by shadow alone Dribbble UI concepts
Glassmorphism Translucent blur for depth Windows 11 Mica, Apple’s Liquid Glass

A frequent claim needs correcting: Apple never adopted neumorphism in its operating systems. The design language Apple announced at WWDC 2025 for iOS 26 onwards, “Liquid Glass,” is translucent material that refracts what is behind it—conceptually the opposite of pushing a same-coloured surface forward with shadow.

Building it: two shadows

The construction is simple: two box-shadows, one dark and one light, offset in opposite directions, assuming a light source at the top left.

.neu {
  background: #e0e0e0;      /* must match the parent background */
  border-radius: 16px;
  box-shadow:
     8px  8px 16px #b8b8b8,  /* shadow, bottom right */
    -8px -8px 16px #ffffff;  /* highlight, top left */
}

Add inset for the pressed state:

.neu--pressed {
  box-shadow:
    inset  6px  6px 12px #b8b8b8,
    inset -6px -6px 12px #ffffff;
}

Picking shadow colours by hand is tedious, so derive them from the background with custom properties and color-mix(). The shadows then survive a theme change.

.neu-theme {
  --neu-bg: #e0e0e0;
  --neu-dark:  color-mix(in oklab, var(--neu-bg), black 18%);
  --neu-light: color-mix(in oklab, var(--neu-bg), white 70%);

  background: var(--neu-bg);
}

.neu {
  background: var(--neu-bg);
  border-radius: 16px;
  box-shadow:
     8px  8px 16px var(--neu-dark),
    -8px -8px 16px var(--neu-light);
}

The technique only works when the element’s background colour exactly matches its parent’s. Any difference reads as an outline rather than relief, and it cannot be placed over photographs or patterns at all.

Sponsored

Why it fails on interactive controls

Three reasons, none of them about taste.

1. It cannot satisfy WCAG 1.4.11

The information “this is a button” is carried by the contrast of its boundary. WCAG 2.2 requires at least 3:1 for that. Neumorphic shadows sit extremely close in lightness to the background, giving a ratio of roughly 1.2–1.5.

In practice: users with low vision cannot see where they are supposed to press.

2. Nothing looks pressable

Cards, buttons, and inputs all become the same raised surface. Because they are visually indistinguishable, users cannot tell what is interactive.

3. It breaks in dark mode

On a dark background the white highlight is too strong and floats, while the dark shadow sinks into the background and vanishes. There is simply less room to work with than in light mode.

Three ways to keep it accessible

The only workable answer is to demote neumorphism to decoration and carry the affordance elsewhere.

1. Always add a border that meets 3:1

.neu-button {
  background: var(--neu-bg);
  border-radius: 16px;

  /* the shadow is decoration; the border carries the boundary */
  border: 1px solid #6b6b6b;   /* 3:1 or better against #e0e0e0 */

  box-shadow:
     8px  8px 16px var(--neu-dark),
    -8px -8px 16px var(--neu-light);
}

It may feel like this dilutes the style, but a UI whose boundaries are invisible is not functioning as a UI. This part is not negotiable.

2. Keep text and icon contrast

Separately from the component boundary, label text needs 4.5:1 (3:1 for large text). Avoid pale grey text on a pale grey background.

.neu-button {
  color: #333;
  font-weight: 600;
}

3. Do not remove the focus ring

Neumorphic examples habitually use outline: none, which destroys keyboard usability. Keep a clear focus indicator that does not disappear into the shadow.

.neu-button:focus-visible {
  outline: 3px solid #1a73e8;
  outline-offset: 3px;   /* push it clear of the shadow */
}

The outline-offset matters: layered on top of the shadow, the outline dissolves visually.

Sponsored

A neumorphic button that reads as pressable

Everything above, applied. Copy it as-is.

:root {
  --neu-bg:    #e0e0e0;
  --neu-dark:  color-mix(in oklab, var(--neu-bg), black 18%);
  --neu-light: color-mix(in oklab, var(--neu-bg), white 70%);
  --neu-border:#6b6b6b;
  --neu-text:  #333;
}

@media (prefers-color-scheme: dark) {
  :root {
    --neu-bg:    #2e3239;
    --neu-dark:  color-mix(in oklab, var(--neu-bg), black 35%);
    --neu-light: color-mix(in oklab, var(--neu-bg), white 12%);
    --neu-border:#9aa0a8;
    --neu-text:  #f2f2f2;
  }
}

body { background: var(--neu-bg); }

.neu-button {
  background: var(--neu-bg);
  color: var(--neu-text);
  border: 1px solid var(--neu-border);
  border-radius: 14px;
  padding: 14px 28px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;

  box-shadow:
     6px  6px 12px var(--neu-dark),
    -6px -6px 12px var(--neu-light);

  transition: box-shadow .15s ease-out, transform .15s ease-out;
}

.neu-button:hover {
  box-shadow:
     4px  4px  8px var(--neu-dark),
    -4px -4px  8px var(--neu-light);
}

.neu-button:active {
  box-shadow:
    inset 5px 5px 10px var(--neu-dark),
    inset -5px -5px 10px var(--neu-light);
  transform: translateY(1px);
}

.neu-button:focus-visible {
  outline: 3px solid #1a73e8;
  outline-offset: 3px;
}

/* drop the shadows and sharpen the edge under high contrast */
@media (prefers-contrast: more) {
  .neu-button {
    box-shadow: none;
    border-width: 2px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .neu-button { transition: none; }
}
<button class="neu-button" type="button">Submit</button>

Note that the dark-mode highlight mixes at only 12%. Keeping the light-mode value of 70% would leave a bright white line floating on the dark surface.

The prefers-contrast: more branch matters too. If someone has asked their OS for high contrast, there is no reason to show them an ambiguous shadow.

Where to use it, and where not to

Use Verdict Why
Decorative cards and panels Good fit Carries no affordance
Toggle and slider tracks Good fit The thumb can carry the contrast
Heavily styled UIs (music players) Conditional Acceptable with borders and labels
Form inputs Avoid The editable region is unidentifiable
Primary actions (buy, submit) Avoid Being obviously pressable outranks style
Errors and warnings Avoid Needs unambiguous colour and shape

Tools

  • Neumorphism.io: set the background colour, distance, and blur and it generates the box-shadow
  • Figma / Adobe XD: stack a Drop Shadow and an Inner Shadow

Pasting generator output alone guarantees you lose contrast and focus styling. After generating, add the border, the text colour, and :focus-visible yourself.

Summary

  • Neumorphism is two box-shadows. The element and its parent must share a background colour
  • Derive shadow colours from the background with color-mix() so themes stay intact
  • Shadows alone cannot meet WCAG 1.4.11’s 3:1. Add a border
  • Text needs 4.5:1. Never write outline: none; keep :focus-visible with outline-offset
  • Reduce the highlight ratio sharply in dark mode
  • Drop the shadows under prefers-contrast: more
  • Do not use it for primary actions or form inputs
  • Apple did not adopt it; their current direction is translucent Liquid Glass

“Feels nice to look at” and “can be operated” are different properties. Keep it decorative and build the important parts as ordinary UI, and neumorphism is still perfectly usable. For state-driven styling, see the CSS :has() selector.