Design Lounge
Install

Nº 539 of 543

Tactile stat tiles

Three raised stat tiles on one putty surface: a 72px answer with a delta in a well pill and a seven-bar chart in a well, two smaller tiles beside it, and a segmented control that swaps Night for Week.

Preview

Open full size
1280 × 800 · 8.0 KB · 97 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 stat tiles" (piece stat-tile-tactile) 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

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

What it is

The morning summary of Hush, a fictional sleep app, as three neumorphic tiles. One is the answer: “Asleep 7h 42min” at 72px, a delta pill pressed into the tile (“38 min more than your average”), and a seven-bar chart sitting in a well, each bar a small raised block with the latest night filled ink. The other two tiles are smaller (36px figures) and carry one sentence each. A segmented control at the top right swaps Night for Week and rewrites every figure, delta and sentence. The detail worth copying is the hierarchy under a style that flattens everything: one display size, pills pressed in rather than coloured, and bars that are raised objects in a pressed tray, so the chart reads without a single hue.

Reference behaviour

  1. First frame: “Last night”, segments Night (pressed) | Week. Tiles: Asleep 7h 42min, delta up “38 min more than your average”, bars Wed to Tue at 62 / 70 / 48 / 80 / 66 / 58 / 90% with Tue filled ink; Deep sleep 1h 51min, delta up “24%”, a sentence; Resting heart 54 bpm, delta down “2 below last week”, a sentence.
  2. On load the bars grow from 20% to full height over 600ms, 40ms apart (reduced motion: drawn at once).
  3. Click Week: the title becomes “This week”; the figures become 7h 04min (avg), 1h 38min, 56 bpm; the deltas and sentences change; the bars stay (they already show the week).
  4. Click Night: back to the first frame’s values.
  5. The pressed segment is raised and ink; the other is flat and --ink-2.
  6. Nothing else is interactive; the tiles have no hover.

Structure

1280 × 800, surface #E6E3DF; a 1040 set centred
Last night 22/800                                           ( Night | Week )  well, pressed raised
┌ tile 1.6fr, r24, raised ─────────────────┐ ┌ tile 1fr ─────────────┐ ┌ tile 1fr ──────────────┐
│ ASLEEP                                   │ │ DEEP SLEEP            │ │ RESTING HEART          │
│ 7h 42min   72/800                        │ │ 1h 51min  36/800      │ │ 54 bpm                 │
│ (↑ 38 min more than your average) well   │ │ (↑ 24%) well          │ │ (↓ 2 below last week)  │
│ ┌ well tray ─────────────────────────┐   │ │ Mostly before 02:00…  │ │ Lowest at 04:10…       │
│ │ ▮ ▮ ▮ ▮ ▮ ▮ █   raised bars, 7    │   │ └───────────────────────┘ └────────────────────────┘
│ └────────────────────────────────────┘   │
│  Wed Thu Fri Sat Sun Mon Tue  11/600     │
└──────────────────────────────────────────┘
  • main.set → div.head (h1, div.seg[role=group] of two aria-pressed buttons), div.tiles.
  • Each section.tile[aria-labelledby] → span.label, div.num (spans for the figures, small for the units), span.delta (an arrow SVG and text), then either div.bars[role=img][aria-label] of seven i plus div.bars-l labels, or a p.

Tokens

:root {
  --surface: #e6e3df;
  --ink: #2a2622;  --ink-2: #56514b;  --ink-3: #625c56;
  --edge: #7d7770;                         /* 1px, 3.5:1 on the surface */
  --light: rgba(255,255,255,.85);  --shade: rgba(74,66,58,.22);
  --raise:    10px 10px 24px var(--shade), -10px -10px 24px var(--light);  /* tiles */
  --raise-sm:  5px  5px 12px var(--shade),  -5px  -5px 12px var(--light);  /* bars, pressed segment */
  --well:     inset 6px 6px 14px var(--shade), inset -6px -6px 14px var(--light);  /* the bar tray */
  --well-sm:  inset 3px 3px 7px var(--shade),  inset -3px -3px 7px var(--light);   /* delta pills, the segment well */

  --sans: "Sora", system-ui, sans-serif;
  --r: 20px;  --r-card: 24px;  --r-tray: 16px;
  --tray-h: 88px;  --gap: 22px;
  --t-micro: 200ms;  --t-bar: 600ms;
  --ease: cubic-bezier(.2, .7, .2, 1);  --expo: cubic-bezier(.16, 1, .3, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
The answerSora72px8000.95−0.045emtabular numerals; units 28px/600 --ink-2
Small tile figureSora36px8000.95−0.045emunits 18px/600
TitleSora22px8001−0.025emsentence
DeltaSora13px60010sentence, in a well pill
SentenceSora13px4001.450sentence, --ink-2
SegmentSora13px60010sentence
LabelSora12px6001+0.06emUPPERCASE, --ink-3
Bar labelsSora11px60010Wed…Tue, --ink-3

Motion

ElementTriggerPropertyFrom → ToDurationEasing
.bars iloadtransform (scaleY, origin bottom)0.2 → 1, delay 0 to 240ms in 40ms steps600ms--expo
.seg buttonpressbox-shadow, colornone, --ink-2 → --raise-sm, --ink200ms--ease
figures, deltas, sentencesrange changetextswapped at once0–

Reduced motion: no bar growth, 1ms transitions.

States

  • Tile: raised, 1px edge, no hover.
  • Delta pill: a well with an arrow; up and down are different arrows, not colours.
  • Bar: raised block with an edge; the latest period is filled --ink.
  • Segment pressed / unpressed: raised ink / flat --ink-2.
  • Focus-visible: 2px ink outline at 4px offset on the segments.

Accessibility

  • Each tile is a <section> labelled by its own label; the figures are real text (“7”, “h”, “42”, “min”), so a screen reader hears “7 h 42 min”.
  • The bar tray is role="img" with an aria-label (“Hours asleep, the last seven nights”), since a label on a plain div is prohibited; the labels under it are visual (aria-hidden) because the sentence and figure carry the data. Add a visually hidden list of the seven values if the chart is the only place they appear in your product.
  • Up and down deltas use different arrow glyphs and words (“more than”, “below”); nothing depends on colour.
  • The segmented control is a role="group" of aria-pressed buttons; the title changes with it.
  • Contrast: --ink 11.7:1, --ink-2 6.1:1, --ink-3 4.9:1 on the surface; --edge 3.5:1 around tiles, pills, bars and the tray.
  • Hit targets: segments 34px tall in a 42px well.

Responsive rules

  • ≥ 1280: three tiles, 1.6fr 1fr 1fr, gap 22.
  • 1024–1279: the same at 1.4fr 1fr 1fr; the answer 60px.
  • 768–1023: the big tile full width, the two small tiles in a row beneath.
  • < 640: one column; the answer 56px; bar tray 72px; the head stacks title then segments.
  • Dark pair: dark putty surface, shade rgba(0,0,0,.45), light rgba(255,255,255,.06), light ink, edge #4a453f; the latest bar fills with the light ink.

Acceptance checklist

Always

  • One display-size figure on the view; the other tiles’ figures are at most half its size.
  • One surface colour; tiles raised, pills and the bar tray pressed in, bars raised inside the tray; no third shadow colour and no hue for emphasis.
  • Every tile, pill, bar and the tray has a 1px edge of at least 3:1 against the surface.
  • Deltas show direction with an arrow and a word, never colour alone.
  • The segmented control is aria-pressed buttons; switching rewrites the title, every figure, delta and sentence.
  • The bars grow in on load, staggered, and are still under reduced motion.
  • Radii 20 to 24px on tiles, 16px on the tray, 999px on pills; bars 6px on top.

This demo

  • Night: 7h 42min, +38 min, 1h 51min (+24%), 54 bpm (2 below); Week: 7h 04min avg, 1h 38min (23%), 56 bpm; bars 62 / 70 / 48 / 80 / 66 / 58 / 90 with Tue filled.

Implementation notes

Raised bars in a pressed tray:

.bars { height: 88px; padding: 10px 12px; border-radius: 16px; box-shadow: var(--well); border: 1px solid var(--edge);
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; align-items: end; }
.bars i { height: var(--h); border-radius: 6px 6px 3px 3px; background: var(--surface); border: 1px solid var(--edge); box-shadow: var(--raise-sm);
  transform-origin: bottom; animation: grow var(--t-bar) var(--expo) both; animation-delay: var(--d); }
.bars i.now { background: var(--ink); border-color: var(--ink); }
@keyframes grow { from { transform: scaleY(.2) } }

Figures as spans, so units stay in the text face at a smaller size and the number stays readable:

<div class="num"><span>7</span><small>h</small><span>42</span><small>min</small></div>

The range swap is a data object and innerHTML on known ids; no framework needed:

const d = D[b.dataset.r];
n1.innerHTML = d.n1; d1.innerHTML = arrow(true) + d.d1; p2.textContent = d.p2; // and so on

Variants. A tile with no chart drops the tray and keeps the sentence; a tile with a target shows it as a second pill (“goal 8h”) in the same well style, never as a coloured ring. For a dark pair, keep the latest bar in the light ink.

Common mistakes: four equal tiles in a row (the answer gets lost); a green or red delta instead of an arrow and a word; a coloured fill on the bars; shadows without the hairline, so the bars merge into the tray on a dim screen.

Details

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

Studied

MindMarket

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

All sources