Design Lounge
Install

Nº 537 of 543

Tactile settings panel

A phone settings screen in monochrome neumorphism: raised rows on one putty surface, inset switches whose knob fills ink when on, a slider in a well, and a segmented control, all edged for 3:1.

Preview

Open full size
390 × 844 · 9.8 KB · 127 lines

Use it

Free to use · credit appreciated

1 Once, in your project · Node 22.20+

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Tactile settings panel" (piece settings-tactile-panel) 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.

Brief

166 lines

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. When a kit is locked, map the surface and inks onto the theme tokens and keep the two shadow tokens and the 1px edge: the edge is what keeps neumorphism legible.

What it is

The settings screen of Warm, a fictional heating app, in monochrome neumorphism. There is one colour, a warm putty #e6e3df, and everything is either raised out of it (an outer pair of shadows: light from the top left, shade to the bottom right) or pressed into it (the same pair, inset). Rows are raised 64px cards with a 20px radius; inside each, an icon sits in a small well, and the control is a switch whose track is a well and whose knob is raised. Turning a switch on slides the knob 26px and fills it ink, so the state is visible without reading depth. A range slider for the target temperature runs in a well with a raised knob that has an ink ring; a segmented control for units is a well with the pressed option raised. The detail worth copying is the 1px edge: every control and row has a #7d7770 hairline, which is 3.5:1 against the surface, so the boundaries pass non-text contrast even where the shadows wash out.

Reference behaviour

  1. First frame: a raised round Back button and “Settings” at the top (62px top padding for the status bar). Three groups: Heating (Target while home 19.5° with a slider, Follow the schedule on, Eco mode off), Display (Units °C | °F, Boiler alerts on), Home (two link rows with chevrons).
  2. Drag the slider: the value updates live (“19.5°” in 0.5 steps from 15 to 24) and aria-valuetext follows. The knob is a raised 30px disc with an ink ring; the track is a 14px well.
  3. Tap a switch: the knob slides 26px over 240ms and turns ink; the small ON/OFF word inside the track swaps sides. Tap again to turn off.
  4. Tap °F: the pressed segment is raised and ink; the temperature reads in Fahrenheit (67°). °C returns it.
  5. Press a link row or the Back button: it goes from raised to pressed (the well) while held, with no other change.
  6. The list scrolls inside the frame; the header stays. Nothing animates on load.

Structure

390 × 844, surface #E6E3DF everywhere (no second background)
top  62/20/10    (◯ back 48)  Settings 30/800
scroll, padding 14/20/40, groups gap 22, rows gap 10
HEATING  12/600 caps
┌ row tall, 20 r, raised ─────────────────────────────┐
│ [well ic 36] Target while home              19.5°   │
│              Holds from 06:30 to 22:00              │
│   (═══════════●══════════════)  slider, well track   │
├ row 64, raised ─────────────────────────────────────┤
│ [ic] Follow the schedule                  (ON  ●)   │  switch 60 × 34, well; knob 26 raised
│      Weekdays and weekends differ                   │
├ row ────────────────────────────────────────────────┤
│ [ic] Eco mode                             (○  OFF)  │
└─────────────────────────────────────────────────────┘
DISPLAY
│ [ic] Units  Shown everywhere         ( °C | °F )    │  segmented: well, pressed option raised
│ [ic] Boiler alerts                        (ON  ●)   │
HOME
│ [ic] Rooms and radiators  5 rooms, 7 valves…    ›   │  button rows
│ [ic] People  Mira, Tej and one guest key        ›   │
  • header.top → button.back[aria-label], h1.
  • main.scroll → three section.group[aria-labelledby], each an h2 then rows.
  • A row is div.row (or button.row.link for navigation) → span.ic (well, aria-hidden SVG), div.t (b title, span caption), then the control: button.sw[role=switch][aria-checked][aria-labelledby] with a span.lab ON/OFF; or div.seg[role=group] of aria-pressed buttons; or input[type=range][aria-labelledby][aria-valuetext] in div.slide under a span.val[aria-live].

Tokens

:root {
  --surface: #e6e3df;   /* the only ground */
  --ink: #2a2622;  --ink-2: #56514b;  --ink-3: #625c56;
  --edge: #7d7770;      /* 1px hairline, 3.5:1 on the surface */
  --light: rgba(255,255,255,.85);
  --shade: rgba(74,66,58,.22);
  --raise:    8px  8px 18px var(--shade), -8px -8px 18px var(--light);   /* rows */
  --raise-sm: 4px  4px 10px var(--shade), -4px -4px 10px var(--light);   /* knobs, back button, pressed segment */
  --well:     inset 6px 6px 12px var(--shade), inset -6px -6px 12px var(--light);  /* a held row */
  --well-sm:  inset 3px 3px 7px var(--shade), inset -3px -3px 7px var(--light);   /* icon wells, tracks, segments */

  --sans: "Sora", system-ui, sans-serif;
  --r: 20px;  --r-card: 24px;
  --row-h: 64px;  --sw-w: 60px;  --sw-h: 34px;  --sw-knob: 26px;  --thumb: 30px;
  --t-micro: 200ms;  --t-knob: 240ms;
  --ease: cubic-bezier(.2, .7, .2, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
TitleSora30px8001−0.03emsentence
ValueSora22px8001−0.02emtabular numerals
Row titleSora16px6001.30sentence
Row captionSora13px4001.40sentence, --ink-2
SegmentSora13px60010as written
Group labelSora12px6001+0.06emUPPERCASE, --ink-3
Switch wordSora10px6001+0.06emON / OFF, --ink-3 off, --ink on

Motion

ElementTriggerPropertyFrom → ToDurationEasing
.sw::after (knob)toggletransform, background0, surface → translateX(26px), --ink240ms / 200ms--ease
.seg buttonpressbox-shadow, colornone, --ink-2 → --raise-sm, --ink200ms--ease
.row.link, .back:activebox-shadow--raise → --well0 (instant)–
slider valueinputtextlive0–

Reduced motion: transitions 1ms; the knob still moves, instantly.

States

  • Row: raised, 1px edge. Link row held: the well. Focus-visible: 2px ink outline at 3px offset.
  • Switch off: well track, knob at left in the surface colour with an edge, the word OFF at the right in --ink-3. On: knob at right filled --ink, the word ON at the left in --ink.
  • Segment: unpressed flat in --ink-2; pressed raised in --ink.
  • Slider: well track, raised knob with a 3px ink ring inside a surface ring; the value beside the title is live.
  • Icon well: a 36px square well with a 12px radius; the icon is currentColor ink.

Accessibility

  • Switches are role="switch" with aria-checked, labelled by the row title; Space toggles. The ON/OFF word is visual only (pointer-events none) and is in addition to the knob’s position and colour.
  • The slider is a native input[type=range] with aria-labelledby and aria-valuetext (“19.5 degrees”), so arrow keys and screen readers work without extra code; the visible value is aria-live="polite".
  • Segments are buttons with aria-pressed in a role="group" labelled “Units”.
  • Link rows are <button>s containing the whole row, with the chevron decorative.
  • Contrast: --ink 11.7:1, --ink-2 6.1:1, --ink-3 4.9:1 on the surface; the --edge hairline 3.5:1 (non-text); the knob when on is --ink on the surface, 11.7:1.
  • Hit targets: rows 64px, switches 60×34 inside a 64px row, back 48px, slider thumb 30px on a 40px-tall strip, segments 30px tall inside a 40px well. Nothing relies on shadow alone.

Responsive rules

  • 390 wide: as drawn. 360 wide: row padding 12px, caption may wrap to two lines.
  • 430 wide: the same; rows stretch.
  • Tablet or desktop: the list is a 560px column, centred, same rows. Do not spread rows wider than 640px; the shadows stop reading as depth.
  • Dark pair: invert the surface to a dark putty and swap the shade/light strengths (shade rgba(0,0,0,.45), light rgba(255,255,255,.06)); the knob-on colour becomes the light ink.

Acceptance checklist

Always

  • One surface colour for the page, rows and controls; depth comes only from the two shadow tokens (--raise outer, --well inset) and their small variants.
  • Every row and control has a 1px edge that measures at least 3:1 against the surface; the shadow is never the only boundary.
  • Switches are role="switch"; on = knob moved 26px and filled with the ink, plus the ON word.
  • The slider is a native range input in a well track with a raised knob, with aria-valuetext and a live visible value.
  • The segmented control is a well whose pressed option is raised.
  • Pressing a link row or the back button shows the well while held.
  • Rows are at least 64px tall; cards and rows use 20 to 24px radii; only knobs, switches and the back button are round.
  • Reduced motion: 1ms transitions.

This demo

  • App “Warm”: Target while home 19.5° (15 to 24 in 0.5 steps), Follow the schedule on, Eco mode off, Units °C | °F, Boiler alerts on, Rooms and radiators, People.
  • °F shows the same target as 67°.

Implementation notes

Two tokens, one surface. Every depth is one of these; never a third shadow colour:

.row { background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r); box-shadow: var(--raise); }
.ic, .sw, .seg, .slide input { box-shadow: var(--well-sm); border: 1px solid var(--edge); }

The switch is a well with a raised pseudo-element knob:

.sw { position: relative; width: 60px; height: 34px; border-radius: 999px; box-shadow: var(--well-sm); }
.sw::after { content: ""; position: absolute; top: 3px; left: 3px; width: 26px; height: 26px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--edge); box-shadow: var(--raise-sm);
  transition: transform var(--t-knob) var(--ease), background var(--t-micro) var(--ease); }
.sw[aria-checked="true"]::after { transform: translateX(26px); background: var(--ink); border-color: var(--ink); }

The slider thumb draws its ring with stacked inset shadows, so it needs no extra markup:

input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 30px; height: 30px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--edge);
  box-shadow: var(--raise-sm), inset 0 0 0 6px var(--surface), inset 0 0 0 9px var(--ink); }

Common mistakes: dropping the hairline because “the shadow shows the edge” (it fails 3:1 on most screens); a coloured accent sneaking in for the on state (use the ink); a page background that differs from the row surface (the light-from-top-left story breaks); shadows so soft the knob disappears on a dim display.

Details

Palette
Type
Sora
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Don't Board Me

A control. It studies this site for quiet, not for a show.

All sources