A designer sent me a Figma file last spring with twelve soft, pillowy buttons on a light grey board and asked why they looked flat the moment they hit the staging site. The CSS had been copied correctly. The shadows were there. What had changed was the section background: the developer had dropped the buttons into a container with a white background, and the entire illusion collapsed. Neumorphism is one of the few UI styles where the element and the thing behind it have to agree, and almost every broken implementation I have looked at since traces back to that one rule.
What Neumorphism Actually Is
Neumorphism, sometimes written as soft UI, makes an element look like it was pushed up from underneath the surface rather than dropped on top of it. There is no floating card and no separate panel. The button appears to be made of the same material as the page, extruded slightly.
That effect comes from a single idea: real objects lit from one direction cast a shadow on one side and catch a highlight on the other. So instead of one shadow, you use two, pointing in opposite directions. One is darker than the background, one is lighter, and both sit on the same element.
This puts it in a different family from the other soft styles. Glassmorphism simulates a frosted pane floating above a busy background and depends on blur. Claymorphism uses a three shadow stack to make chunky, rounded, toy-like objects. Neumorphism does neither. It flattens everything into one continuous surface and uses light alone to suggest depth.
The Dual-Shadow Recipe
Here is the complete effect. Everything else in this article is a variation on these four lines.
.neu-card {
background: #e0e0e0; /* must match the parent */
border-radius: 20px;
box-shadow:
8px 8px 16px #bebebe, /* dark, bottom right */
-8px -8px 16px #ffffff; /* light, top left */
}
Per the MDN box-shadow reference, each shadow reads as offset-x, offset-y, blur-radius, spread-radius, then color, with the length values interpreted in that order. In the snippet above there is no spread, so the three numbers are horizontal offset, vertical offset, and blur.
Three constraints make or break it:
- The offsets must mirror each other. If the dark shadow is
8px 8px, the light one has to be-8px -8px. Break the symmetry and the light source becomes incoherent, which reads as a smudge rather than a raised surface. - The element background must match the parent background. Not approximately. Exactly. The moment they differ, the eye sees an edge, and an edge means a separate object sitting on top, which is the opposite of what the style is for.
- The shadow colors are derived from the background, not picked. Take the base color and lighten it about 10 to 12 percent for the highlight, darken it by the same amount for the shadow. On
#e0e0e0that lands you near#ffffffand#bebebe.
MDN also notes that with multiple comma-separated shadows, the first one specified is painted on top. That rarely matters when the offsets are symmetrical, but it starts to matter the moment you add a third shadow or use large spread values that overlap.
Why It Breaks on White (and on Images)
This is the failure I opened with, and it is worth being precise about the cause.
The highlight shadow is lighter than the background. If the background is already #ffffff, there is no lighter color available. The highlight renders as white on white, which is nothing at all, and you are left with a plain drop shadow on the bottom right. The button does not look extruded. It looks like a normal card with a slightly odd shadow.
The same logic rules out pure black backgrounds in the other direction, and it rules out photographic or gradient backgrounds entirely. A gradient parent means the color behind the element changes across its width, so a single flat element background cannot match it everywhere. You get a visible rectangle.
Practically, neumorphism needs a mid-tone, flat, solid background. Light grey around #e0e0e0 is the usual choice. Dark mode works too, around #2e3440, but with a caveat covered further down.
Also Read: Liquid Glass in 2026 covers the opposite approach, where the background is supposed to show through rather than match.
Pressed States: Flipping the Shadows Inward
The reason neumorphism works well for controls is that it has a natural pressed state. Add the inset keyword to both shadows and the element appears to sink into the surface.
.neu-button {
background: #e0e0e0;
border: none;
border-radius: 16px;
padding: 16px 28px;
box-shadow:
6px 6px 12px #bebebe,
-6px -6px 12px #ffffff;
transition: box-shadow .18s ease;
}
.neu-button:active,
.neu-button[aria-pressed="true"] {
box-shadow:
inset 6px 6px 12px #bebebe,
inset -6px -6px 12px #ffffff;
}
MDN describes inset as changing the shadow from an outer box-shadow to an inner one, as if the content is pressed into the box, and notes that inset shadows are clipped to the padding box. That clipping is why the pressed state stays crisp at the corners instead of bleeding outward.
Note the aria-pressed selector alongside :active. A toggle button that only changes on :active looks pressed while the mouse is held down and then springs back, which is wrong for anything that holds state. Tying the inset state to the ARIA attribute keeps the visual and the semantics in sync.
Building a Neumorphic Switch
The switch is the component people search for most in this style, and it is a genuinely good fit, because a physical switch really does sit in a recessed track.
The construction is two parts with opposite shadow directions. The track is inset, so it reads as a groove cut into the surface. The knob is outset, so it reads as a raised object sitting in that groove.
.neu-switch {
position: relative;
width: 84px;
height: 44px;
border-radius: 22px;
background: #e0e0e0;
box-shadow:
inset 4px 4px 8px #bebebe,
inset -4px -4px 8px #ffffff;
}
.neu-switch .knob {
position: absolute;
top: 5px;
left: 5px;
width: 34px;
height: 34px;
border-radius: 50%;
background: #e0e0e0;
box-shadow:
4px 4px 8px #bebebe,
-4px -4px 8px #ffffff;
transition: transform .22s ease;
}
.neu-switch input:checked ~ .knob {
transform: translateX(40px);
}
Build this on top of a real checkbox input rather than a styled div. Visually hide the input, keep it focusable, and drive the knob from :checked. That gives you keyboard operation and screen reader semantics without writing any JavaScript.
There is a real problem here that the next section deals with directly: in the snippet above, the on state and the off state differ only by the knob’s position. There is no color change, no label change, and no contrast change. For a significant number of users that is not a detectable difference.
Also Read: 3 Ways to Build an Animated Toggle Switch covers the general-purpose toggle patterns, including the accessible markup this section builds on.
The Accessibility Problem, With Numbers
Neumorphism has a structural accessibility issue, and it is not a matter of taste or careful color picking. It follows from the definition of the style.
WCAG Success Criterion 1.4.11 Non-text Contrast, Level AA, requires that visual information required to identify user interface components and states have a contrast ratio of at least 3:1 against adjacent colors. A button’s boundary is exactly that kind of information.
In neumorphism, the only thing marking the boundary is the shadow. And the shadow is derived from the background by design, which caps how different it can be. Running the WCAG contrast formula over the palettes in common use gives this:
| Palette pair | Colors | Ratio | 1.4.11 (needs 3:1) |
|---|---|---|---|
| Classic base vs dark shadow | #e0e0e0 / #bebebe | 1.41:1 | Fail |
| Classic base vs light shadow | #e0e0e0 / #ffffff | 1.32:1 | Fail |
| Dark shadow vs light shadow | #bebebe / #ffffff | 1.86:1 | Fail |
| Blue-grey variant | #e0e0e0 / #a3b1c6 | 1.65:1 | Fail |
| Dark mode, light edge | #2e3440 / #3b4252 | 1.24:1 | Fail |
| Dark mode, dark edge | #2e3440 / #23272f | 1.20:1 | Fail |
Every one fails, and the best case is barely above half the required ratio. This is not a palette that was chosen badly. Push the shadows far enough apart to reach 3:1 and they stop reading as shadows on a shared surface, which means you are no longer building neumorphism.
So treat the shadow as decoration and give every interactive element a second, compliant signal:
- A text label with normal body-text contrast, which is governed by 1.4.3 and unaffected by the shadow work.
- A visible focus ring that does meet 3:1. Do not remove the default outline without replacing it.
- A state indicator that is not purely positional. On the switch above, add a color fill or a check glyph to the on state.
- A real border on form inputs, where the boundary carries meaning rather than style.
Used that way the style is defensible. Used as the sole indicator of what is clickable, it is not.
Neumorphism vs Claymorphism vs Glassmorphism
| Aspect | Neumorphism | Claymorphism | Glassmorphism |
|---|---|---|---|
| Core mechanic | Two mirrored shadows | Three stacked shadows | Backdrop blur plus transparency |
| Background needed | Flat mid-tone, must match | Any solid, usually colorful | Busy or colorful, must show through |
| Reads as | Extruded from the surface | Soft 3D object on the surface | Frosted pane above the surface |
| Boundary contrast | Structurally weak | Workable | Depends on what is behind |
| Render cost | Low | Low | Higher, blur is expensive |
Neumorphism is the cheapest of the three to render, since box-shadow costs far less than backdrop-filter. If you are already watching paint cost on a heavy page, that matters. Our notes on Elementor Core Web Vitals go into where those costs actually show up.
Building It in Elementor
Elementor’s box shadow control exposes horizontal offset, vertical offset, blur, spread, and color, but it gives you one shadow per element. Neumorphism needs two. There are two practical routes.
Custom CSS on the widget. Give the container a class and write the full two-shadow declaration in the Custom CSS panel. This is the honest approach for the effect and takes about a minute per component. Define the base color once as a global color so the shadow derivation stays consistent.
Nested containers. Put an inner container inside an outer one, apply the dark shadow to one and the light shadow to the other. This keeps everything inside the visual editor, at the cost of an extra DOM node per component. Reasonable for a handful of hero elements, not for a component library.
Whichever route you take, set the section background and the widget background to the same global color rather than typing the hex twice. The single most common way this effect breaks in a page builder is that someone updates the section background later and the elements inside it are left behind. Building a reusable design system in Elementor V4 covers the class and variable setup that prevents exactly this drift.
The Plus Addons for Elementor is useful here mostly for the hover and interaction layer rather than the shadows themselves. The pressed and hover transitions are what make a soft UI feel physical, and getting those consistent across a set of widgets is more work than the base shadow. Our guide to CSS hover effects in Elementor covers that side.
When to Use It, and When Not To
Neumorphism works when the interface is small, self-contained, and the controls are obviously controls from context. Media players, thermostat and smart home dashboards, calculators, audio interfaces, and settings panels all fit, because the user already knows a dial is a dial.
It works badly for anything where discovery matters. Marketing pages, checkout flows, forms filled in by first-time visitors, and dense content pages all depend on the user finding the interactive element quickly, and a 1.4:1 boundary does not support that. It also does not survive a mixed background, which rules out most page templates that alternate section colors.
The most successful use I have seen treats it as a texture for one section rather than a whole design language. A neumorphic settings card on an otherwise conventional page gives you the tactile quality without asking the entire site to live under its constraints.
Frequently Asked Questions
Why Does My Neumorphic Button Look Flat?
Almost always because the parent background is white or does not match the element background. The light shadow needs a background darker than itself to be visible. Move the component onto a mid-tone surface such as #e0e0e0 and set the element background to the identical value.
Can Neumorphism Be Made Accessible?
The shadow boundary itself cannot reach the 3:1 required by WCAG 1.4.11 without ceasing to look like neumorphism. The style can still be used accessibly by treating the shadow as decoration and carrying identification through a compliant text label, a visible focus ring, and a non-positional state indicator.
Does Neumorphism Work in Dark Mode?
Yes, with a base around #2e3440 and shadows lightened and darkened from it. The contrast picture is slightly worse than in light mode, since the ratios compress at the dark end. Our measurements put a typical dark palette at 1.20:1 to 1.24:1 against 1.41:1 for the light equivalent.
Is Neumorphism Bad for Performance?
No. It is the cheapest of the soft UI styles because box-shadow does not force the compositing work that backdrop-filter does. Animating box-shadow on many elements at once can cost more than animating transform, so drive motion with transforms where you can.
What Background Color Should I Use?
A flat mid-tone. Light grey near #e0e0e0 is the default. Avoid pure white, pure black, gradients, and images, since all four remove the room the paired shadows need to work in.






