Design Lounge
Install

Nº 020 of 520

Grouped settings list

An iOS-style settings screen: inset grouped sections with 44px rows, coloured icon squares, 51×31 spring toggles with a stretching knob, chevron rows with values, footers and a profile row.

Preview

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

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Grouped settings list" (piece ios-grouped-settings) 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

202 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.

What it is

The Settings screen of “Kestrel”, a phone OS-style app, in the iOS grouped-list idiom. Everything is a white 12px-radius group on a #f2f2f7 page: a profile header row with a 58px avatar, then sections of 44px rows, each with a 29px coloured rounded-square icon, a label, an optional grey value and either a chevron or a switch. Separators are 1px hairlines that start at the label’s left edge, not the group’s. Switches are 51×31 with a 27px knob that springs 20px across in 280ms and stretches to 32px wide while pressed, mirroring the platform’s feel. Each section ends with a 13px footer note. The top bar is a fixed glass strip with a centred “Settings” title that the list scrolls under. The value of the piece is the exactness: get the row height, the inset separator and the switch geometry right and it reads as native.

Reference behaviour

  1. Initial state: “Settings” title in a glass bar; profile row “Kari Hovden / Kestrel ID, Family Sharing, storage” with avatar “KH”; three sections (Connectivity, Notifications & Focus, General) with footers. Toggles: Aeroplane Mode off, Personal Hotspot on, Auto-Brightness on, Raise to Wake off.
  2. Scroll: the list slides under the glass top bar (blur 20px, 82% page-colour fill, hairline bottom border). The bar does not change.
  3. Tap a switch: aria-checked flips; the track colour transitions #e9e9eb ↔ #34c759 over 280ms; the knob translates 0 ↔ 20px over 280ms cubic-bezier(.32,.72,0,1).
  4. Press and hold a switch: the knob stretches to 32px wide (from 27) over 160ms; when the switch is on, the stretched knob also shifts left 5px so its right edge stays put. Release completes the flip.
  5. Press a link row: the whole row’s background becomes --line (#e5e5ea) while pressed. Rows are links (they would push a detail screen).
  6. Space or Enter on a focused switch flips it. Tab moves through the profile row, then each row’s link or switch in order.
  7. Nothing else animates; the piece is about static precision plus the switch.

Structure

390 × 844
┌────────────────────────────────────────┐
│ (54 status)  glass bar 98 tall         │
│               Settings (17/600)        │
│────────────────────────────────────────│ hairline
│ 110 ┌────────────────────────────────┐ │
│     │ (KH)  Kari Hovden           ›  │ │  profile row 76 min-height
│     │       Kestrel ID, Family…      │ │
│     └────────────────────────────────┘ │
│     Signed in on 3 devices…  (footer)  │
│     ┌────────────────────────────────┐ │
│     │ [◐] Aeroplane Mode        (○ ) │ │  44px rows, icon 29
│     │     ─────────────────────────  │ │  separator inset 57px
│     │ [◐] Wi-Fi         Halden-5G ›  │ │
│     │ [◐] Bluetooth           On  ›  │ │
│     │ [◐] Mobile Data             ›  │ │
│     │ [◐] Personal Hotspot     ( ●)  │ │
│     └────────────────────────────────┘ │
│     Hotspot uses your mobile data…     │
│     ┌────────────────────────────────┐ │
│     │ [◐] Notifications           ›  │ │
│     │ [◐] Sounds & Haptics        ›  │ │
│     │ [◐] Focus              Work ›  │ │
│     │ [◐] Screen Time  2h 14m today ›│ │
│     └────────────────────────────────┘ │
│     …                                  │
└────────────────────────────────────────┘
  16px page gutters; groups are full width inside them
  • <header class="bar">: fixed, height:98px; padding-top:54px, centred text.
  • <main>: scroller, padding: 110px 16px 40px.
  • .group: white, radius 12px, overflow:hidden, 8px below; followed by <p class="foot"> with 26px below.
  • .row: flex, min-height:44px, padding: 0 16px, gap 12px. Variants: <a class="row link"> (chevron) and <div class="row"> containing <button class="tog" role="switch" aria-labelledby>. ::after draws the separator from left: 16 + 29 + 12 = 57px; the last row in a group has none.
  • .ic: 29px square, radius 7px, white 18px SVG icon, background from the icon palette.
  • .profile: min-height:76px, padding:12px 16px, 58px avatar, 20px name, 13px sub-line.

Tokens

:root {
  /* colour — system light grouped palette */
  --bg: #f2f2f7;            /* page */
  --surface: #ffffff;       /* groups */
  --ink: #1c1c1e;
  --ink-2: #6e6e73;         /* values, footers */
  --ink-3: #aeaeb2;
  --line: #e5e5ea;          /* separators, pressed row */
  --chev: #c7c7cc;          /* chevrons */
  --accent: #0a7aff;        /* focus rings */
  --on: #34c759;  --off: #e9e9eb;  --knob: #ffffff;
  --glass: rgba(242,242,247,.82);  --glass-line: rgba(28,28,30,.1);

  /* icon squares */
  --ic-red: #ff3b30;  --ic-orange: #ff9500;  --ic-green: #34c759;  --ic-blue: #0a7aff;
  --ic-indigo: #5856d6;  --ic-teal: #30b0c7;  --ic-gray: #8e8e93;  --ic-pink: #ff2d55;
  --avatar-gradient: linear-gradient(135deg, #b9c8b0, #5f7d63);

  /* type */
  --font: "Inter", system-ui, -apple-system, sans-serif;

  /* geometry */
  --status: 54px;  --bar-h: 44px;  --content-top: 110px;
  --inset: 16px;  --row-h: 44px;  --row-gap: 12px;
  --icon: 29px;  --r-icon: 7px;  --r-group: 12px;
  --sep-inset: 57px;                        /* inset + icon + gap */
  --tog-w: 51px;  --tog-h: 31px;  --knob-d: 27px;  --knob-travel: 20px;  --knob-stretch: 32px;
  --knob-shadow: 0 3px 8px rgba(0,0,0,.15), 0 1px 1px rgba(0,0,0,.16);
  --avatar: 58px;  --profile-h: 76px;
  --chevron: 8px 14px;

  /* motion */
  --t-micro: 160ms;
  --t-toggle: 280ms;
  --spring: cubic-bezier(.32, .72, 0, 1);
  --ease: cubic-bezier(.2, .7, .2, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
Row labelInter17px4001.30title case (platform convention)
Row valueInter17px4001.30--ink-2
Bar titleInter17px6001−0.01emsentence
Profile nameInter20px5001.25−0.015emas written
Profile subInter13px4001.30sentence, --ink-2
FooterInter13px4001.40sentence, --ink-2
AvatarInter20px60010initials

Motion

ElementTriggerPropertyFrom → ToDurationEasingNotes
.togflipbackground#e9e9eb ↔ #34c759280ms--ease
.tog::afterfliptransformtranslateX(0) ↔ 20px280ms--spring
.tog::after:activewidth27px → 32px160ms--easewhen on, transform becomes 15px so the right edge holds
.row:activebackgroundwhite → --line160ms--easelink rows

Reduced motion: all transitions 1ms; the switch still flips and the knob still stretches (instantly).

States

  • Switch on: aria-checked="true", green track, knob right. Off: grey track, knob left.
  • Switch pressed: knob 32px wide. Switch focus-visible: 2px --accent outline, 3px offset.
  • Row pressed: --line background. Row focus-visible: 2px --accent outline, outline-offset:-2px, 8px radius (inside the group’s clip).
  • Row with value: value text in --ink-2 immediately left of the chevron.
  • Disabled (spec only): row text --ink-3, icon at 50% opacity, switch at 40% opacity and aria-disabled.
  • Profile row: taller (76px), avatar instead of icon, two-line label.

Accessibility

  • Switches are <button role="switch" aria-checked aria-labelledby="<label id>">; Space and Enter toggle; the visible label is the accessible name.
  • Link rows are <a> elements whose text is label + value (“Wi-Fi Halden-5G”); chevrons are decorative SVG.
  • Icons are decorative (<span class="ic"> with an SVG and no text); the label carries meaning.
  • Focus order follows the visual order top to bottom.
  • Contrast: --ink-2 on white 4.9:1; footers at 13px pass AA; --chev is decorative.
  • Hit targets: rows 44px tall and full width; switches 51×31 inside a 44px row (the whole row height is the target on touch if you forward row taps to the switch, which the platform does).

Responsive rules

  • 360 wide: identical; long values (“2h 14m today”) may truncate the label first (label has text-overflow:ellipsis), never the value.
  • ≥ 430 wide: content column max-width: 430px centred, bar title centred over it.
  • Tablet: two-pane, this list as a 320px sidebar with the selected row highlighted in --accent at 12% and the detail on the right.
  • Dark mode (if your product has it): page #000, groups #1c1c1e, separators #38383a, off-track #39393d; icon colours unchanged.

Acceptance checklist

  • Rows are exactly 44px min-height with 16px horizontal padding; the profile row is 76px.
  • Separators are 1px #e5e5ea, start 57px from the group’s left edge, and the last row in a group has none.
  • Icon squares are 29×29 with a 7px radius and an 18px white stroke icon; colours come from the eight icon tokens.
  • Groups are white with a 12px radius, 8px below each group and 26px below each footer.
  • Switches are 51×31 with a 27px knob at 2px inset; on-state translates the knob exactly 20px; track colours are #e9e9eb / #34c759.
  • Knob travel uses cubic-bezier(.32,.72,0,1) over 280ms; pressing stretches the knob to 32px in 160ms without moving its outer edge.
  • Switches use role="switch" + aria-checked and respond to Space and Enter.
  • Chevrons are 8×14 in #c7c7cc; values sit in #6e6e73 immediately left of the chevron.
  • The top bar is fixed at 98px including the status area, glass-filled, and the list starts at 110px.
  • Every row and switch shows a visible focus ring that is not clipped by the group.
  • No text uses the pure #000 or #fff outside the knob and icon glyphs.

Implementation notes

The switch is one button and one pseudo-element. Keep the geometry in tokens; the pressed stretch when “on” shifts the knob left by the stretch amount so its right edge stays fixed:

.tog { position: relative; width: 51px; height: 31px; border-radius: 16px; background: var(--off); border: 0; padding: 0;
       transition: background 280ms var(--ease); }
.tog::after { content: ""; position: absolute; left: 2px; top: 2px; width: 27px; height: 27px; border-radius: 50%;
       background: #fff; box-shadow: 0 3px 8px rgba(0,0,0,.15), 0 1px 1px rgba(0,0,0,.16);
       transition: transform 280ms cubic-bezier(.32,.72,0,1), width 160ms var(--ease); }
.tog[aria-checked="true"] { background: var(--on); }
.tog[aria-checked="true"]::after { transform: translateX(20px); }
.tog:active::after { width: 32px; }
.tog[aria-checked="true"]:active::after { transform: translateX(15px); }
document.querySelectorAll('.tog').forEach(t => {
  const flip = () => t.setAttribute('aria-checked', String(t.getAttribute('aria-checked') !== 'true'));
  t.addEventListener('click', flip);
  t.addEventListener('keydown', e => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); flip(); } });
});

Inset separators without extra markup — draw them on the row’s ::after and hide the last one:

.row { position: relative; }
.row::after { content: ""; position: absolute; left: 57px; right: 0; bottom: 0; height: 1px; background: var(--line); }
.group .row:last-child::after { display: none; }

Common mistakes: using <input type="checkbox"> with appearance:none but forgetting the label association; separators drawn on the group with full width; border-radius on rows instead of the group; using height:44px (rows with wrapped values overflow) instead of min-height; forgetting -webkit-backdrop-filter.

Details

Palette
Type
Inter
Motion
Subtle motion
Build
Quick build
Designed bySusan AcharyaKathmandu

Studied

Tailwind CSS docs

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

All sources