Hewyn interaction and material kit

How our surfaces are built and how they move

Every widget below is live. Press it, drag it, hold it. This page exists so a decision about motion or material can be felt on a phone rather than argued about in a document. Each widget is a class in hewyn-ui.css, so the dashboard, the report engine and the purchase page adopt it by changing a class name.

One

Surface and material

Rounded boxes are the named design element, so they are a system rather than a habit. Four radii plus a pill, each with one job. Four surface behaviours, and a surface is exactly one of them. Separation comes from shadow and ground, not from a grey line around everything.

Radius scale

Pick by the job, not by the look. Five values in the whole product, and a child's radius is its parent's minus the padding between them. A sheet is a container, so it takes the container radius rather than one of its own.

pill
999px
pills, toggles, action buttons
xs
6px
chip, input, swatch, skeleton line
sm
10px
control, list row, inner tile
md
16px
the card radius, the default
lg
24px
sheet, container, dark hero blocks

Nested radius is optically corrected

An inner radius equals the outer radius minus the padding between them. Reuse the outer number and the inner corner looks fat and the gap looks uneven. Left is correct: a 24px container with 8px of padding holds a 16px card, which holds a 10px photo inside 6px of padding. Right repeats 24px at every level.

Correct. 24, then 16, then 10.

Wrong. 24 everywhere. The corners crowd each other and the gap reads as uneven even though it is not.

Elevation is shadow, never a border

Three levels. e1 is a single contact shadow, a hairline of depth. e2 and e3 pair that contact shadow with a wide ambient one that lifts the surface, because one blurred shadow on its own looks like a sticker. Every layer is tinted to the ink hue and sits between 4 and 18 percent, so the stack never greys the page.

e0 flush
e1 contact only
e2 resting card
e3 hover, selected, sheet
inset track, well

Four surface behaviours

Raised

A card above the ground. White fill, ink-tinted shadow, no border. Use for anything a customer can act on or read as a unit.

Flush

Same plane as its ground. No shadow at all. Separated by fill alone. Use for grouping, context blocks, and anything that is not tappable.

Inset

Pressed into the ground. Inner shadow. Use for tracks, wells and gutters: the toggle track, the zone band, the segmented control's background.

Glass

Only ever over a photograph or over content that is still scrolling behind it. Over a flat ground it is just a dirty panel.

Hairlines sit at partial opacity

The default hairline is ink at 8 percent and separates rows inside a surface. The strong one, ink at 14 percent, opens a section, rings a secondary action and draws a focus ring. A hairline is never a card edge and never a button border, and a solid grey line is what makes a page look like a spreadsheet.

Correct. --h-line, ink at 8 percent. It reads as an edge, not as an object.

Wrong. A solid grey. It has its own colour temperature and fights the warm paper.

Two

Motion tokens

Four durations and four curves, declared once in snippets/hewyn-os-tokens.liquid and aliased here. Everything in the kit uses them and nothing declares its own milliseconds.

Duration

--t-tap120ms. Finger down, finger up, and any state change. Has to feel instant. Aliased here as --h-t-press.
--t-reveal200ms. A thing appearing in place, or a knob or indicator travelling. Aliased as --h-t-quick and --h-t-move, which were two names for one idea.
--t-panel320ms. A disclosure, a sheet, a drill-down. Aliased as --h-t-enter.
--t-draw600ms. A data mark drawing itself: a ring, a bar, a marker travelling to its zone. Long on purpose, because it is being read rather than operated. Aliased as --h-t-draw.
--t-fade120ms. Opacity, and the one duration that survives reduced motion, so a state change stays perceivable when every travel is gone.

Easing

--h-ease-outcubic-bezier(.22, 1, .36, 1). The workhorse. Fast out of the gate and a long settle. This is what makes a press feel answered rather than played back.
--h-ease-movecubic-bezier(.65, 0, .35, 1). Moving between two known positions: the segment indicator, a marker sliding to its zone.
no springThe kit used to carry two overshooting curves for the toggle knob, the sheet and a selection landing. They are retired. An overshoot is a claim that a control has mass, and in a product whose surfaces are mostly measurements that claim reads as the interface editorialising about the data. Everything that used to spring now settles on --h-ease-out.
--h-ease-in-outcubic-bezier(.4, 0, .2, 1). A value moving between two known points. No character, and that is correct for data.
--h-ease-exitcubic-bezier(.4, 0, 1, 1). Something leaving. It should not linger.

Two rules that are not negotiable

  • Transform and opacity only. No CSS transition or keyframe in the kit touches width, height, margin, padding or a position offset, so no interaction in the system can cause a reflow. The one exception is the disclosure, which interpolates height through the Web Animations API on a panel carrying contain: content, which keeps that reflow inside the panel.
  • Reduced motion collapses, it never breaks. A single multiplier goes to 0 in the shared snippet and every duration goes to one frame with it. Opacity stays at 120ms, so a state change is still perceivable. Every control still works and still gives feedback, through elevation and colour instead of travel.

Three

Widgets that move

All of these are live and all of them use Alex's real demo numbers. Press and hold to feel the asymmetry: going down is instant, coming back up has weight.

Card, press and settle

Down at 120ms on ease-out, up at 200ms on the same curve. The shadow drops with it, which is what sells the press as physical. Hover lifts one pixel and raises to e3.

Toggle, with travel

The knob travels the full track and settles on ease-out, which is what an accept or reject decision should feel like. Hold it and the knob squashes on the X axis the way a physical switch compresses, then returns to round. On is ink rather than the optimal green, because a status colour is never a fill.

Vitamin D3, 4000 IU

Morning stack. Added because vitamin D is 24.6 ng/mL.

Omega-3, 2g EPA and DHA

Morning stack. Omega-3 Index 6.1 percent.

Locked by your clinician

Disabled state.

Option tile, selection you can feel

The selected ring is an inset box-shadow, never a border, so the box size does not change and no tile beside it moves by a pixel. The warm fill fades in on a pseudo-element and the check scales in on ease-out. Selecting raises the tile a level and rings it in ink, so material carries the state rather than colour. First group is single select, second is multi.

Chips scale down 5 percent on touch and invert to ink when on. The last one is disabled, and it does not move at all, which is the only honest way to say no to a tap.

Progress ring, drawing and interpolating

Draws from zero on entry over 600ms, and animates between values rather than jumping. The numeral rolls digit by digit, least significant first, rather than swapping. Press a system to move it. Ring colour is a status colour, never the action orange.

Zone band, marker travels on entry

The marker starts at the left edge and travels to its position over 600ms, which makes a reader notice where the value came from. Zones carry the status tints, never orange.

LDL cholesterol

152 mg/dL

0Optimal under 130200

Omega-3 index

6.1 percent

2Optimal above 812

Rolling number, tabular figures

Each digit is a column of zero to nine translated on the Y axis, and each cell is a fixed tabular width. Nothing beside the number shifts, including the unit, even when the value crosses from two digits to three.

Disclosure, interpolated height

Opens over 320ms so the content below slides rather than being shoved, and the chevron turns on the same curve. The panel carries contain: content, so the reflow stays inside the panel and never reaches the page.

Your vitamin D reads 24.6 ng/mL against an optimal band of 30 to 100, and your DNA report shows a higher than average vitamin D requirement. Both point the same way, which is why this sits at the top of the morning stack.

Four of five systems held or improved. Omega-3 index moved from 5.4 to 6.1, VO2 max from 43.7 to 44.9, and biological age from 41.8 to 41.2 against a chronological age of 45.

Omega-3 index5.4 to 6.1
VO2 max43.7 to 44.9
Biological age41.8 to 41.2

Segmented control, sliding indicator

One indicator, moved with translateX and scaled on the X axis to the active segment's width, so segments of different widths work and nothing animates a layout property. Arrow keys move the selection.

Showing all 53 markers from the 26 June panel.

Bottom sheet

Springs up over 320ms, blurs the backdrop, scrolls internally, and can be dragged down by the grip. While your finger is down the transition is removed so the sheet tracks exactly, then restored on release so it springs home or away. The page behind never moves.

Skeletons, not spinners

A spinner says wait. A skeleton says this is the shape of what is coming, which makes the same wait feel shorter and stops the layout jumping when the content lands. The shimmer is a transform-only sweep, so it runs on the compositor.

Metabolic

Your engine room is strong

HbA1c 5.4 percent, fasting glucose 88

Four

Photography as a design element

Four treatments and nothing else. Every one of them puts a controlled layer between the photograph and the type, so nothing ever lands on raw image. Aspect ratio is always declared in CSS, never inherited from the file, so swapping an image can never change the height of a page.

A. Full bleed with a scrim

Wind down

Your evening stack

Magnesium, and lights down by ten.

The scrim runs from 78 percent ink at the type edge to fully clear at 72 percent up the frame, so the half with no type on it is still a photograph. Type sits in the bottom left at 22px of padding and is capped at 26 characters per line, which keeps it clear of the subject in every crop we shoot.

B. Inset in a rounded card

Nutrients

What to eat this cycle

Focus

The midday dip

The photo's radius is the card's radius minus the card's padding: 20 minus 10 is 10. The image scales up 3 percent inside a fixed frame on hover, so the frame itself never moves. That is the difference between an expensive card and a cheap one.

C. Duotone on the ink palette

Treated. Desaturated, then multiplied under an ink to sage duotone and screened back. It stops being stock and starts being Hewyn.

Untreated. The same file. Its own colour temperature fights the ink ground and pulls the eye off whatever is beside it.

D. Section divider

A wide, short strip at 32 by 9, feathered into the ground at both edges, carrying no type at all. That is the point of it. It is a breath between two blocks of content, not a slide.

The rules

  • Ratios. 3 by 2 for a content photo, 4 by 5 for a portrait or a product in a scene, 32 by 9 for a divider, 1 by 1 for a thumbnail. Always declared in CSS. The page hero is the single exception that changes with the viewport: 4 by 5 on a phone, 16 by 9 from 600px, 21 by 9 from 1000px, because it is the only photo that has to hold a headline and a fixed 21 by 9 is 167px tall on a phone.
  • Scrim depth. 78 percent ink under the type, clearing by 72 percent of the frame. If type still fails contrast at that depth, the crop is wrong, not the scrim.
  • Where type sits. Bottom left, 22px in, capped at 26 characters per line. Never centred over a subject and never in the middle of a frame.
  • Treatment on ink. Anything on the dark ground gets the duotone. An untreated photograph on ink always looks like it was pasted there.

And what a photograph must never do

  • Never compete with data. A photo and a number do not share a card. If a customer has to read a value, the photo goes above it or beside it, not behind it.
  • Never sit behind a number. No value, marker, score or range ever renders over an image, at any scrim depth.
  • Never carry a claim. A photograph cannot imply a result or a medical conclusion. No before and after, no clinical setting, no white coat, nothing that reads as proof of an outcome.
  • Never be the only thing. A photo-only section says nothing. Every photograph on a Hewyn screen either sets a scene for copy or is the touch target for a real destination.

Five

Composed

Three real Hewyn screens built only from the parts above, so the kit gets judged as a whole rather than as a parts bin.

A results card

Blood panel, 26 June

Heart

Your LDL and omega-3 index point the same direction, and your calcium score is still zero. There is no plaque to undo yet, which makes this the ideal window to act.

LDL 0Optimal under 130200
LDL 152 ApoB 112 Calcium score 0

A routine action row

Card press, toggle, disclosure and status pill in one row. Accepting an item is the most repeated interaction in the product, so it gets the most physical control in the kit.

Vitamin D3, 4000 IU

Morning, with fat. Retest in 12 weeks.

Vitamin D 24.6 ng/mL against an optimal band of 30 to 100, and a higher than average requirement on your DNA report.

Two oily fish meals a week

Plate change. Omega-3 index 6.1 percent.

An omega-3 index of 6.1 percent sits below the 8 percent band. Food moves this as reliably as a capsule does, so both are on the plan and you can take either.

A test card

Photo inset in a rounded card, nested radius, a status pill, and the one orange action on the whole card. This is the purchase page pattern.

Body composition

Done, 88

DEXA scan

Body fat 18.2 percent, bone Z-score plus 0.8.

Digestion

Not tested

Gut microbiome

At home sample, results in about three weeks.

Six

How the existing sections adopt this

The kit was built so that adoption is a class change plus one data attribute, never a rewrite. Add the two assets to a section's head, then swap the classes below. Nothing in hewyn-ui.css targets a tag or an existing section class, so the two systems can run side by side while a section is migrated one widget at a time.

Step one, in any section

tokensNothing to do on the three app surfaces: layout/app.liquid already renders {% render 'hewyn-os-tokens' %} and loads both kit assets for every page on it. Anywhere else, render the snippet first. Every --h-* name is an alias of a token in it and resolves to nothing without it.
headAdd {{ 'hewyn-ui.css' | asset_url }} as a stylesheet and {{ 'hewyn-ui.js' | asset_url }} as a deferred script, in that order, after the snippet.
rootAdd hui to the section's root element. That is what scopes every token. Add hui-ground-ink or hui-ground-paper to any block that changes ground.

Step two, the swaps

dashboardbutton.rcard becomes class="hui-card hui-press". The system ring markup becomes <div class="hui-ring" data-hui-ring data-value="78"> and the existing score numeral is deleted, because the ring builds its own rolling one.
dashboardbutton.stackcard becomes hui-disc with a hui-disc__head and a hui-disc__panel, which removes the section's own open and close height code.
dashboard[data-toggle=accepted] becomes class="hui-toggle" data-hui-toggle and listens for hui:toggle instead of its own click handler.
reportMarker range rows become hui-band with data-hui-band and a ratio. Category accordions become hui-disc. The lens switcher becomes hui-seg.
blood reportThe marker browser becomes a hui-sheet, which gets it drag to dismiss, backdrop blur and correct iOS scroll locking for free.
purchase page.hdx-card becomes hui-photo-card hui-press with the test photograph in a hui-photo hui-photo--3x2. The selected state becomes hui-tile, which is what stops the current 1px border from shifting the grid on selection.
onboardingEvery option becomes hui-tile with data-hui-tile="groupname" for single select. The progress header becomes a hui-ring. The assembling screen's placeholder becomes hui-skel plus hui-swap.

Each of those is independent. A section can take the toggle this week and the ring next month, and the two look consistent in the meantime because the kit's resting states were drawn to match what is on the site today.

What not to do

  • Do not add a component rule to a section. If a widget needs a variant, it goes in hewyn-ui.css as a modifier so every surface gets it.
  • Do not hard code a duration. Use the tokens. A stray 300ms is how a system stops feeling like one hand made it.
  • Do not use orange for a status. It is the action colour and nothing else. A score, a band, a flag and a trend all use ok, watch or attention.
  • Do not animate a layout property. If a motion seems to need width or height, it almost always wants transform and a fixed frame instead.

Blood panel, 26 June

Cholesterol profile

LDL cholesterol152

Optimal under 130 mg/dL

ApoB112

Optimal under 90 mg/dL

Triglycerides168

Optimal under 150 mg/dL

Total cholesterol224

Optimal under 200 mg/dL

HDL cholesterol52

Optimal above 40 mg/dL

Lp(a)18

Optimal under 75 nmol/L

Drag the grip down, tap the backdrop, or press escape. The page behind has not moved.