Nº 416 of 520
Soft inset clay buttons
Neumorphic buttons, icon toggles, switches and a segmented control on a clay surface, with AA text, a hairline edge and colour as the second state signal.
Preview
Use it
Free to use · credit appreciated1 Once, in your project
npx skills add SusanAcharya/Design-Lounge 2 Then ask
Using the Design Lounge skill, build "Soft inset clay buttons" (piece button-inset-soft) in my stack. Match its brief; restyle it onto my product's theme.
No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.
<!-- Design Lounge Nº 416 · "Soft inset clay buttons" · designlounge.vercel.app -->
# Soft inset clay buttons
> **Build brief for a coding agent.** Rebuild this piece in the reader's stack. If they haven't said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don't "improve" them.
## What it is
The control panel of a ceramics kiln controller, "Kilnhouse", built in soft UI. Everything is the same terracotta-clay colour; shape comes only from a paired light and dark shadow. Buttons are raised pillows that sink into the clay when pressed. Toggles stay sunk while on. A switch and a three-way segmented control sit in carved wells. The piece is worth copying because it fixes the two things that make neumorphism fail: every control also has a 1px hairline edge so it reads without the shadows, and every "on" state also turns its label or icon ember-red, so state never relies on depth alone. All text clears 4.5:1.
## Reference behaviour
1. First frame: a raised clay stage, radius 40px. Top left "Kilnhouse" in Gloock 34px and "Kiln 2 · Stoneware · target 1,222°C". Top right a carved status pill with a dot: "Idle · 24°C".
2. Left column "Buttons · L / M / M / S": Start firing (large, ember text, small ember dot), Hold temp, Vent, Unload (small, disabled, dashed outline).
3. Below that "Icon toggles · 56px": three round toggles. Chamber light starts on (sunk, ember icon); Exhaust fan and Lock door start off.
4. Right column "Toggles": Auto vent switch (on, ember knob slid right, "On"), Slow cool switch (off, "Off"). Below, "Programme": Bisque / Glaze / Raku segmented control with Glaze selected.
5. Hover on a raised button: its shadow grows from 6px to 9px offset, so it lifts towards you.
6. Press: the button flips to an inset shadow and scales to 0.985 in 90ms. Release returns over 200ms.
7. Start firing toggles: it stays sunk with `aria-pressed="true"`, its label becomes "Stop firing", and the status reads "Firing · ramp 150°C/h" with the dot turning ember with a 3px halo. Pressing again restores "Idle · 24°C".
8. Hold temp sets the status to "Holding at 1,040°C" and Vent to "Vent opened for 30 s"; after 2.4s the status returns to the firing or idle line.
9. Icon toggles flip `aria-pressed`; on = inset + ember icon.
10. Switches flip `aria-checked`; the knob slides 30px in 280ms, becomes ember, and the track tints ember at 14%. The trailing word flips On/Off.
11. Segmented control: click or arrow keys move selection; a raised thumb slides under the chosen label in 320ms, and the label turns ember 600.
12. A "States" sheet underneath shows Primary, Raised, Icon toggle and Switch at Default, Hover, Pressed, Disabled, Focus.
13. Reduced motion: all transitions removed, no press scale.
## Structure
```
1280 × 800, stage width min(1060px, 100%), padding 34/40/32, radius 40
┌───────────────────────────────────────────────────────────────────────────┐
│ Kilnhouse (Gloock 34) (● Idle · 24°C) well │
│ Kiln 2 · Stoneware · target 1,222°C │
│ │
│ BUTTONS · L / M / M / S 1.5fr │ TOGGLES 1fr │
│ (● Start firing) (Hold temp) (Vent) [Unload]│ (◯●) Auto vent On │
│ 60px 52px 52px 42px │ (●◯) Slow cool Off │
│ ICON TOGGLES · 56PX │ PROGRAMME │
│ (bulb) (fan) (lock) │ [ Bisque |(Glaze)| Raku ] │
│───────────────────────────── 1px hairline ────────────────────────────────│
… 153 more lines. Copy or download for the full brief. Loading the source…Brief
193 linesBuild brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.
What it is
The control panel of a ceramics kiln controller, “Kilnhouse”, built in soft UI. Everything is the same terracotta-clay colour; shape comes only from a paired light and dark shadow. Buttons are raised pillows that sink into the clay when pressed. Toggles stay sunk while on. A switch and a three-way segmented control sit in carved wells. The piece is worth copying because it fixes the two things that make neumorphism fail: every control also has a 1px hairline edge so it reads without the shadows, and every “on” state also turns its label or icon ember-red, so state never relies on depth alone. All text clears 4.5:1.
Reference behaviour
- First frame: a raised clay stage, radius 40px. Top left “Kilnhouse” in Gloock 34px and “Kiln 2 · Stoneware · target 1,222°C”. Top right a carved status pill with a dot: “Idle · 24°C”.
- Left column “Buttons · L / M / M / S”: Start firing (large, ember text, small ember dot), Hold temp, Vent, Unload (small, disabled, dashed outline).
- Below that “Icon toggles · 56px”: three round toggles. Chamber light starts on (sunk, ember icon); Exhaust fan and Lock door start off.
- Right column “Toggles”: Auto vent switch (on, ember knob slid right, “On”), Slow cool switch (off, “Off”). Below, “Programme”: Bisque / Glaze / Raku segmented control with Glaze selected.
- Hover on a raised button: its shadow grows from 6px to 9px offset, so it lifts towards you.
- Press: the button flips to an inset shadow and scales to 0.985 in 90ms. Release returns over 200ms.
- Start firing toggles: it stays sunk with
aria-pressed="true", its label becomes “Stop firing”, and the status reads “Firing · ramp 150°C/h” with the dot turning ember with a 3px halo. Pressing again restores “Idle · 24°C”. - Hold temp sets the status to “Holding at 1,040°C” and Vent to “Vent opened for 30 s”; after 2.4s the status returns to the firing or idle line.
- Icon toggles flip
aria-pressed; on = inset + ember icon. - Switches flip
aria-checked; the knob slides 30px in 280ms, becomes ember, and the track tints ember at 14%. The trailing word flips On/Off. - Segmented control: click or arrow keys move selection; a raised thumb slides under the chosen label in 320ms, and the label turns ember 600.
- A “States” sheet underneath shows Primary, Raised, Icon toggle and Switch at Default, Hover, Pressed, Disabled, Focus.
- Reduced motion: all transitions removed, no press scale.
Structure
1280 × 800, stage width min(1060px, 100%), padding 34/40/32, radius 40
┌───────────────────────────────────────────────────────────────────────────┐
│ Kilnhouse (Gloock 34) (● Idle · 24°C) well │
│ Kiln 2 · Stoneware · target 1,222°C │
│ │
│ BUTTONS · L / M / M / S 1.5fr │ TOGGLES 1fr │
│ (● Start firing) (Hold temp) (Vent) [Unload]│ (◯●) Auto vent On │
│ 60px 52px 52px 42px │ (●◯) Slow cool Off │
│ ICON TOGGLES · 56PX │ PROGRAMME │
│ (bulb) (fan) (lock) │ [ Bisque |(Glaze)| Raku ] │
│───────────────────────────── 1px hairline ────────────────────────────────│
│ STATES DEFAULT HOVER PRESSED DISABLED FOCUS │
│ Primary (Fire) … │
│ Raised (Vent) … │
│ Icon toggle (lock) … │
│ Switch (◯●) … │
└───────────────────────────────────────────────────────────────────────────┘
- Stage is
mainlabelled by theh1. - Status is a
p aria-live="polite". - Buttons are
button type="button"; Start firing gainsaria-pressedafter first press. - Icon toggles are
buttonwitharia-labelandaria-pressed. - Switches are
button role="switch" aria-checked, the visible text is the name; the On/Off word isaria-hidden. - Segmented control is
div role="radiogroup"labelled by its caption, with threebutton role="radio"using rovingtabindex; the thumb is anaria-hiddenspan. - The states grid is
inertandaria-hidden, with a visually hidden sentence before it.
Tokens
:root {
--clay: #e3cbb8; /* surface: page, stage and every control */
--hi: #f7e8db; /* light shadow (top-left) */
--lo: #b8957d; /* dark shadow (bottom-right) */
--ink: #3a261b; /* text ~10:1 */
--ink-2: #6b4a38; /* captions ~5:1 */
--ember: #8f3219; /* the one accent: on-state text, knob, dot ~5:1 */
--line: rgba(90,55,35,.14); /* hairline on every raised control */
--off-ink: #a08573; /* disabled text */
--serif: "Gloock", Georgia, serif;
--sans: "Lexend", system-ui, sans-serif;
--ease: cubic-bezier(.2,.7,.2,1);
--out: cubic-bezier(.16,1,.3,1);
--raise: 6px 6px 14px var(--lo), -6px -6px 14px var(--hi);
--raise-hi: 9px 9px 20px var(--lo), -9px -9px 20px var(--hi);
--sink: inset 4px 4px 9px var(--lo), inset -4px -4px 9px var(--hi);
--well: inset 3px 3px 7px var(--lo), inset -3px -3px 7px var(--hi);
}
Stage shadow: 14px 14px 34px var(--lo), -14px -14px 34px var(--hi) plus 1px solid rgba(255,255,255,.35).
Sizes: L 60px tall / 32px padding / radius 20; M 52 / 26 / 18; S 42 / 18 / 14; icon 56px circle. Switch track 64×34, knob 26. Segmented: 5px well padding, items 42px tall, thumb radius 14, well radius 18.
Spacing: 4, 6, 10, 14, 16, 22, 26, 32, 40.
Typography
| Role | Family | Size | Weight | Tracking | Case |
|---|---|---|---|---|---|
| Wordmark | Gloock | 34px / 1 | 400 | −0.01em | Title |
| Meta, status | Lexend | 13px | 400 / 500 | 0 | Sentence |
| Button labels | Lexend | 14 / 15 / 16px | 500 (primary and on 600) | 0 | Sentence |
| Captions, column heads | Lexend | 11px / 10px | 500 | 0.12em | Upper |
| On/Off word | Lexend | 12px | 400 | 0 | Title |
Gloock appears once. Never set button labels in it.
Motion
| Thing | Trigger | Property | From → to | Duration | Easing | Reduced motion |
|---|---|---|---|---|---|---|
| Lift | :hover | box-shadow | raise → raise-hi | 200ms | —ease | none |
| Press | :active | box-shadow, scale | raise → sink, 1 → .985 | 90ms | —ease | shadow swap only |
| Toggle on | aria-pressed | box-shadow, colour | raise → sink, ink → ember | 200ms | —ease | instant |
| Switch knob | aria-checked | translateX, fill | 0 → 30px, clay → ember | 280ms | —out | instant |
| Segment thumb | selection | translateX | n × 100% | 320ms | —out | instant |
| Status dot | firing | fill | ink-2 → ember | 240ms | —ease | instant |
States
- Default raised:
--raise+ 1px--lineborder. - Hover:
--raise-hi. - Pressed (momentary):
--sink, scale .985. - On / selected:
--sinkand ember text or icon at 600. This is the non-depth signal. - Disabled: no shadow, 1px dashed
rgba(90,55,35,.28),--off-inktext,cursor: not-allowed. Disabled switch knob#d9bfab, flat. - Focus-visible: 2px
--emberoutline, offset 4px. Switch draws it on the track, not the whole label row. Segments draw it inset (offset −3px) so it stays inside the well.
Accessibility
- Contrast: ink on clay ~10:1,
--ink-2~5:1,--ember~5:1. The disabled--off-inkis intentionally below 4.5 (disabled controls are exempt) but the dashed outline still marks the shape. - Shadows alone are under 3:1. The hairline, the dashed disabled outline and the ember on-state carry the meaning.
- Switches: Space and Enter toggle; name comes from the visible label, state from
aria-checked. - Segmented: Tab enters on the selected radio; Left/Up and Right/Down move and select, wrapping around.
- Icon toggles: fixed labels (“Chamber light”, “Exhaust fan”, “Lock door”) with
aria-pressed. - Status is the only live region.
- Hit targets ≥ 42px; switch row ≥ 44px.
Responsive rules
- ≥ 1024: two columns, 1.5fr / 1fr, gap 32.
- < 860: one column; stage padding 26/20, radius 28; the segmented control goes full width; the states grid drops its label column (labels span above each row) and shrinks controls to 40px.
- At 375 the four text buttons wrap onto two lines; nothing overflows.
- Do not use this on a white or dark page. Soft UI only works when the page and the controls are the same colour.
Acceptance checklist
Always
- Page, stage and controls share one surface colour.
- Raised = two outer shadows (light top-left, dark bottom-right); pressed/on = the same pair inset.
- Every raised control has a 1px hairline edge.
- On states change colour as well as depth.
- Disabled controls are flat with a dashed outline.
- All body and label text ≥ 4.5:1.
- Switches use
role="switch"; segments use a radiogroup with arrow keys. - Focus ring 2px accent, offset 4px, visible on every control.
- A states sheet shows default, hover, pressed, disabled and focus, and is inert.
- Reduced motion removes all transitions.
This demo
- Brand “Kilnhouse”, status “Idle · 24°C”, toggling Start firing gives “Stop firing” and “Firing · ramp 150°C/h”.
- Chamber light and Auto vent start on; Glaze starts selected.
- Clay
#e3cbb8, shadows#f7e8db/#b8957d, ember#8f3219. - Hold temp and Vent show their message for 2.4s.
Implementation notes
Shadows as tokens. Define the four shadow recipes once; every control picks one:
.soft { background: var(--clay); border: 1px solid var(--line); box-shadow: var(--raise);
transition: box-shadow 200ms var(--ease), transform 200ms var(--ease), color 200ms var(--ease); }
.soft:hover { box-shadow: var(--raise-hi); }
.soft:active { box-shadow: var(--sink); transform: scale(.985); transition-duration: 90ms; }
.soft[aria-pressed="true"] { box-shadow: var(--sink); color: var(--ember); font-weight: 600; }
.soft:disabled { box-shadow: none; border: 1px dashed rgba(90,55,35,.28); color: var(--off-ink); }
Segment thumb. One absolutely positioned thumb, a third of the inner width, moved by percent of its own width:
function pick(i, focus) {
radios.forEach((r, j) => { r.setAttribute('aria-checked', j === i); r.tabIndex = j === i ? 0 : -1; });
thumb.style.transform = `translateX(${i * 100}%)`;
if (focus) radios[i].focus();
}
Thumb CSS: top:5px; left:5px; width:calc((100% - 10px)/3); height:calc(100% - 10px).
Common mistakes:
- Pale grey text on the clay because “soft”. It fails contrast; use
--ink. - Depth as the only state signal. Users with low vision cannot see a 4px inset.
- Shadows on a page colour that differs from the control colour; it turns into a cheap bevel.
- Pure white highlight. The light shadow is a warm
#f7e8db, not#fff. - Blurring the shadows past ~20px on small controls; they turn to fog.
Details
- Palette
- Type
- Gloock · Lexend
- Motion
- Subtle motion
- Build
- One session
- Tags
Studied
WickretLet the big words move. Letters can break apart and come back as new words.
All sourcesOn the shelves