Surfaces, chrome and feedback

Source: src/styles/parts/chrome.scs — and lib::surface, lib::raised, lib::floating, controls::tabs, controls::tag for the same objects inside a shadow root.

Border, fill, radius and shadow each say “separate object”, so they are spent by role. A .card and a .panel are bordered surfaces with no shadow: they sit in the page. Popovers, drawers and toasts get a shadow, because they genuinely float above it. A shadow on every block flattens the hierarchy instead of building one.

Cards

A card

new

The body is whatever the page puts in it: the card only supplies the surface, the padding and the gap between its children.

.card.link

A card that is itself the link: it lifts on hover instead of underlining.

.card.flat

No hairline, a quieter ground: a card inside another surface, where a second border would only add noise.

One

280px minimum, then as many per row as fit.

Two

The gap is the section gap.

Three

Resize the window to see it reflow.

Panels

A panel is a card whose header and body are separated by a hairline rather than by padding: it is the frame around a table, a chart or a form, where the content runs to the edges. .panel-head, .panel-body and .panel-foot are there for a markup that cannot use <header> and <footer>.

Recent activity

A panel's body is padded; a table or a list put straight inside the panel is not, and runs to the hairlines.

Updated 2 minutes ago

Widgets

The figure tile of a dashboard: a quiet header, one big number, and the change that number just made. .delta.up and .delta.down are the only two colours on it.

Revenue
48 200 € 12,4 %
Churn
1,8 % 0,3 pt
Open invoices
148

The draggable, resizable version is the element <mmo-widget>, inside an <mmo-grid>.

Tabs

A strip is a segmented control: a track, with the active tab filled with the accent. That is the default because a tab almost always switches a view of the same thing, where nobody should have to look for which one is on — and because the fill means the same thing in both themes, which a pill raised out of the track does not. Mark the active one with .active, aria-selected="true" or aria-current — all three are painted.

.line is the quiet variant, for navigating rather than switching: the underline belongs to the strip, so the active tab sits on the content rather than above it.

The same two forms are what <mmo-tabs> paints in its own shadow root, from the same controls::tab* fragments — a tab looks like a tab whether a page wrote it or an element did.

Toolbars

Saved

Tags and status

State reads at a glance because it has a shape as well as a colour — a dot for live state, a plain pill for a label. Colour alone would carry nothing for a colour-blind reader.

draft scs v0.7 with an icon Neutral Active Pending Failed Trial .status-tag .big

Callouts

A note inside the flow: a left rule in the tone's colour, the tone's soft ground, and a .title line that says what it is.

NeutralNo tone: an aside that is not a warning, an error or good news.
InfoSomething worth knowing before going on.
SuccessWhat just happened, and what it changed.
WarningA trap worth naming before someone falls into it.
ErrorWhat went wrong, and what to do about it.

Floating surfaces

These carry lib::floating: the surface, a hairline and the large shadow. They are shown here in place; in an application they are positioned by the element that opens them.

A popover is the same floating surface with whatever the page puts in it.

The behaviour — anchoring, opening, closing on an outside click — belongs to <mmo-dropdown>.

Shortcut: ⌘K

Overlays

.backdrop, .drawer, <dialog> and .modal are fixed to the viewport, so these two specimens really open them — and the html is what opens them.

Delete this invoice?

This cannot be undone. The document stays in the archive for the legal period.

Toasts

Feedback stacks from the bottom right, newest nearest the cursor, and says what happened — not “success”. The tone is carried by the left rule alone.

Invoice issuedINV-2026-0148 was sent to Hydra Agency.
Two payments are lateBoth are over 30 days.
Export failedThe report server did not answer.
A new version is availableReload to pick it up.

In an application the shell owns the stack: MMO.app.notify(…) and <mmo-notification>.

Loading

A skeleton keeps the layout it will be replaced by, so nothing jumps when the data lands. It is a shimmer over the two quiet grounds, and it swallows pointer events.

Avatars

MM MM MM

The sticky aside

.aside is a column that sticks to the top of its container and never stretches to its height — the rail beside an article, the summary beside a form.

The column the reader is in. The block on the right is an .aside: scroll a long page and it stays with them.

Highlighting a target

Anything linked to by a fragment — :target — or given .highlight-target flashes the warning ground once and fades out, so a reader who followed a deep link sees where they landed.