---
name: soft-ui
description: Use when you want "Friendly, calm, minimal" - A broader soft-shadow style with gentle contrast and rounded surfaces.
---

# Soft UI

> **Category:** Glass / Depth  -  **Personality:** Friendly, calm, minimal
>
> **Best use:** Product cards, forms, wellness apps
>
> **Ratings:** Professional 4/5 - Casual 5/5 - Exotic 2/5 - Visual impact 3/5 - Difficulty 2/5 - Performance cost 2/5

## What it is

Soft UI is the gentle, friendly cousin of neumorphism. Every surface shares the **same background colour as the page**, then gets a *pair* of `box-shadow`s — a light one toward the light source (top-left) and a darker one away from it (bottom-right) — so the element looks softly **extruded** from the page. Swap those shadows to `inset` and the same element looks **pressed in**, which is how you show "on", "selected" or "active" states. Unlike strict neumorphism it keeps a little more contrast and allows an accent colour, so it stays usable — ideal for product cards, forms and calm wellness apps.

## Dependencies / CDN

None - pure CSS for the effect (vanilla JS only flips state classes). The demo loads two display/UI fonts — optional:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=Quicksand:wght@500;600;700&display=swap" rel="stylesheet">
```

## HTML

The whole language is just "a surface on the surface". A card, a raised button, and a toggle that flips to pressed:

```html
<div class="su-stage">
  <div class="su-app">
    <button class="su-btn">Begin session</button>

    <div class="su-row">
      <span>Sleep stories</span>
      <!-- role=switch + aria-checked = accessible; .is-on flips the look -->
      <button class="su-toggle is-on" type="button" role="switch" aria-checked="true" aria-label="Sleep stories"></button>
    </div>
  </div>
</div>
```

## CSS

```css
/* 1) ONE surface colour everywhere — page and elements MUST match */
.su-stage{
  --su-bg:#e8ece4;        /* the single surface colour            */
  --su-light:#fbfdf7;     /* highlight  (toward the light source) */
  --su-dark:#c6ceba;      /* shadow     (away from it)            */
  background:var(--su-bg);
  border-radius:28px; padding:54px 44px;

  /* THE EFFECT — reusable shadow tokens */
  --su-raise:7px 7px 16px var(--su-dark), -7px -7px 16px var(--su-light);
  --su-press:inset 5px 5px 12px var(--su-dark), inset -5px -5px 12px var(--su-light);
}

/* 2) Extruded surface = the raised pair */
.su-app,.su-btn{ background:var(--su-bg); border-radius:24px; box-shadow:var(--su-raise); }

/* 3) Press it in on interaction = the SAME shadow, inset */
.su-btn{ border:0; padding:14px 22px; cursor:pointer; transition:box-shadow .2s, transform .2s; }
.su-btn:hover{ transform:translateY(-2px); }
.su-btn:active{ transform:translateY(0); box-shadow:var(--su-press); }

/* 4) A neumorphic switch: track is a groove, knob is a raised pill */
.su-toggle{ width:58px; height:32px; border-radius:999px; border:0; cursor:pointer;
  background:var(--su-bg); box-shadow:inset 3px 3px 7px var(--su-dark), inset -3px -3px 7px var(--su-light);
  position:relative; transition:box-shadow .22s; }
.su-toggle::after{ content:""; position:absolute; top:4px; left:4px; width:24px; height:24px;
  border-radius:50%; background:var(--su-bg); box-shadow:var(--su-raise);
  transition:transform .24s cubic-bezier(.4,1.25,.5,1), background .2s; }
.su-toggle.is-on{ box-shadow:inset 4px 4px 9px #3c6c56, inset -4px -4px 9px #84b6a1; } /* accent groove */
.su-toggle.is-on::after{ transform:translateX(26px); background:#fff; }
```

The same two tokens (`--su-raise` / `--su-press`) drive the card, buttons, stat tiles, segmented mood control, slider track/thumb and the breathing ring in the demo — that consistency is what makes it read as one tactile material.

## JavaScript

Not required for the look. The demo's JS only toggles state classes so a surface flips between raised and pressed (here, the accessible switch):

```js
document.querySelectorAll('.su-toggle').forEach(function(t){
  t.addEventListener('click', function(){
    var on = t.classList.toggle('is-on');          // CSS does the visual flip
    t.setAttribute('aria-checked', on ? 'true' : 'false');
  });
});
```

The mood segments work the same way (`.is-active` raises the chosen one out of an inset well), and a range `input` just writes its value into a label.

## How it works

- **Two shadows, opposite corners.** A light shadow toward the light source + a dark shadow away from it tricks the eye into reading a soft bevel. There is no border and no fill change — the shadows *are* the shape.
- **Element colour == page colour.** The illusion only holds while the surface and its background are the same colour; a contrasting fill turns it back into a normal shadowed box.
- **`inset` = pressed.** Flipping the exact same offsets to `inset` carves the shape inward, giving you free "selected/active/on" states.
- **Rounded corners + generous blur** keep it gentle; tight radii or hard shadows read as plastic, not soft.

## Customization

- **Depth:** bigger offset + blur (`10px 10px 24px`) floats higher; smaller (`4px 4px 10px`) sits closer. Keep the light/dark offsets symmetric.
- **Light direction:** flip the sign of the offsets to move the imagined light (e.g. lit from bottom-right).
- **Contrast / softness:** widen the gap between `--su-light` and `--su-dark` for crisper relief, narrow it for a milkier, calmer surface.
- **Accent:** allow one colour (here a sage `#3f6f59`) for the active groove, the progress ring and the primary button — sparingly, so it stays "soft".

## Accessibility & performance

- **Contrast is the classic Soft UI failure.** Low-contrast monochrome surfaces can drop text and controls below WCAG AA — keep body text dark (the demo uses `#3c463f`/`#5f675e` on `#e8ece4`) and give the primary action a filled accent so it's unmistakable.
- **Don't rely on shadow alone to signal state.** Pair it with `role="switch"` + `aria-checked` / `aria-pressed`, real `<button>`s, and a visible `:focus-visible` outline (offset so it clears the soft shadow).
- **Cheap to render** vs. `backdrop-filter`: static box-shadows are GPU-friendly. Animate `transform`/`opacity`, never the shadow's blur radius, to stay at 60fps.
- **Reduced motion:** gate any looping animation (the demo's "breathing" ring) behind `@media (prefers-reduced-motion: reduce){ … animation:none }`.

## Gotchas

- **Mismatched background kills it.** Put a Soft UI card on a white or photographic page and it just looks like a grey box with weird shadows — the parent must be the same flat colour.
- **`overflow:hidden` clips the glow.** Outer shadows need breathing room; give containers padding (or they get sliced at the edges).
- **Pure neumorphism ≠ Soft UI.** Going fully monochrome and ultra-low-contrast is the trap; Soft UI deliberately keeps gentle contrast + an accent so forms remain readable and tappable.
- **Symmetry matters.** If the light and dark offsets/blur aren't mirrored, the bevel looks lopsided rather than softly raised.
