Navigation

v0.0.4
Materials

Surface

Trays, sheets and wells: the material ladder every component is built on.

On this page

Every Master UI component is made of the same few materials. Use them directly for cards, panels and groups so custom layouts share the components' light and depth. The theme explains the ladder.

Tray, sheet and well

<div class="tray">  <div class="sheet flex flex-col gap-md p-lg">    <p class="m(0) text-sm fg-text-strong">Deployment</p>    <div class="well p-md text-sm fg-text-muted">No builds are queued.</div>  </div></div>

#Sheet

A sheet is a raised face with a 16px corner, drawn as a squircle where the browser can. It catches the light on its top edge, takes a half-pixel contour and settles on a short shadow that fades out. The default sheet is lifted; choose a lower or higher step by its role.

Sheet elevations

<div class="sheet sheet-flat p-lg text-sm fg-text-muted">Flat</div><div class="sheet sheet-raised p-lg text-sm fg-text-muted">Raised</div><div class="sheet p-lg text-sm fg-text-muted">Lifted</div><div class="sheet sheet-floating p-lg text-sm fg-text-muted">Floating</div>
ClassElevationUse for
sheet sheet-flatOne contour, no shadowDense lists and tables
sheet sheet-raised--shadow-raisedSmall cards on a busy surface
sheet--shadow-liftedCards and panels
sheet sheet-floating--shadow-floatingPopovers and menus over content
sheet sheet-inverseThe lifted shadow, on inkThe one surface on a page that asks for something

#Inverse

sheet-inverse is a sheet of ink: dark on moonstone and pearl on obsidian. It is the face of a primary button at the size of a surface, with the same fall of light and the same line of light along its top. Use one on a page, for the moment that asks for something, such as a call to action.

Words on it take the inverse ink: add fg-text-inverse to a heading, which is in strong ink by default. A default button on it is the brightest thing in view, so it reads as the action; a primary button would be ink on ink.

Inverse

<section class="sheet sheet-inverse flex flex-wrap items-center justify-between gap-md p-lg">  <div class="flex flex-col gap-3xs">    <h3 class="heading heading-sm fg-text-inverse">Your trial ends in three days</h3>    <p class="m(0) text-sm">Add a card to keep your projects online.</p>  </div>  <button type="button" class="btn btn-sm">Add a card</button></section>

#Well

A well is recessed below its surroundings: a soft inset shadow, one half-pixel contour and a lip of light under its bottom edge. Fields, segmented tracks and switches are wells.

--well-radius changes the well's circular radius before the theme's corner scale. Its default is var(--radius-control).

Well

<div class="well p-lg text-sm fg-text-muted">Drop files here</div>

#Tray

A tray is a recessed holder for one or more sheets. Its 4px inset makes the radii nest: the tray's radius is the sheet's scaled radius plus that inset. With the default 1.6 corner scale, the 16px surface radius becomes 25.6px and the tray's radius becomes 29.6px.

Tray

<div class="tray">  <p class="m(0) px-md py-sm text-xs fg-text-muted">Recent</p>  <div class="sheet p-lg text-sm fg-text-body">Quarterly report.pdf</div></div>

A different component held directly in a tray needs the sheet's radius too. Set the property on that held root: --well-radius for a well, --code-radius for a code block or panel, --window-radius for a window, or --alert-radius for an alert. Use var(--radius-surface) as the value. The tray adds its 4px inset after the corner scale, so the relationship follows both the default theme and Paper. Components farther inside keep their own radii.

A well held in a tray

<div class="tray">  <div class="well p-lg text-sm" style="--well-radius: var(--radius-surface)">No builds are queued.</div></div>

#The surface color

Each surface publishes its color as --surface-color. Components that cut a shape out of the surface behind them read it: the ring around a corner badge, around stacked avatars and around a status dot. Inside a sheet, a tray, a well, a card or a dialog the ring therefore matches by itself. On a surface of your own, set the property to its background color.

#Compose with utilities

The same materials are theme tokens, so utilities can paint a custom element: bg-surface-raised, shadow-raised, shadow-pressed and b-line-contour.

Materials as utilities

<div class="p-lg r-lg bg-surface-raised shadow-raised text-sm fg-text-body">bg-surface-raised shadow-raised</div>

#Classes

ClassDescription
sheetA raised face with a 16px corner, a half-pixel contour and the lifted shadow
sheet-flatOne contour and no shadow, for dense lists and tables. With sheet
sheet-raisedThe raised shadow, one step below the default, for small cards on a busy surface. With sheet
sheet-floatingThe floating surface and shadow, for popovers and menus over content. With sheet
sheet-inverseA sheet of ink with the inverse text color, for the one surface on a page that asks for something. With sheet
wellA recessed surface with a 10px corner and an inset shadow
trayA recessed holder for one or more sheets, with a 4px inset and a 20px corner around their 16px

#Custom properties

PropertyDefaultDescription
--well-radiusvar(--radius-control)Circular radius before the corner scale; use var(--radius-surface) on a well held directly in a tray. On well
--surface-colorvar(--color-surface-raised)The color of the sheet, read by a ring that cuts a shape out of the surface behind it; a floating and an inverse sheet set their own. On sheet
--surface-colorvar(--color-surface-inset)The color of the well, read by a ring that cuts a shape out of the surface behind it. On well
--surface-colorvar(--color-surface-tray)The color of the tray, read by a ring that cuts a shape out of the surface behind it. On tray

#Source

@master/ui/components/surface.css
/* * Surfaces: the material ladder in markup. * tray (a recessed holder) → sheet (a raised face) → floating; a well sits below its surroundings. * A sheet can also be ink, for the one surface on a page that asks for something. * Radii nest by a 4px inset: tray 20 → sheet 16, and a control inside is 10. * Each surface publishes its color as --surface-color, so a ring that cuts a shape out of it * (a corner badge, a stack of avatars) matches whatever it sits on. */@layer components {  .sheet {    --surface-color: var(--color-surface-raised);    position: relative;    border-radius: calc(var(--radius-surface) * var(--corner-scale));    corner-shape: var(--corner-shape);    @apply --surface-sheet;    color: var(--color-text-body);    box-shadow: var(--shadow-lifted);    &.sheet-raised {      box-shadow: var(--shadow-raised);    }    &.sheet-floating {      --surface-color: var(--color-surface-floating);      background: var(--color-surface-floating);      box-shadow: var(--shadow-floating);    }    &.sheet-flat {      background: var(--color-surface-raised);      box-shadow: var(--shadow-flat);    }    /*     * Inverse: a sheet of ink, pearl on obsidian. It is the face of a primary button at the size of a surface:     * the same fall of light, the same line of light on its top edge. Use one on a page, for the moment     * that asks for something. Words on it take the inverse ink; a default button on it reads as the action.     */    &.sheet-inverse {      @apply --on-ink;      --surface-color: var(--color-surface-inverse);      @apply --surface-ink;      color: var(--color-text-inverse);      box-shadow:        inset 0 .5px 0 var(--color-ink-highlight),        0 0 0 .5px var(--color-ink-edge),        0 1px 2px -1px var(--color-shade-1), 0 2px 4px -2px var(--color-shade-2), 0 4px 8px -4px var(--color-shade-3), 0 8px 16px -8px var(--color-shade-4);    }    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }  .well {    --well-radius: var(--radius-control);    --surface-color: var(--color-surface-inset);    border-radius: calc(var(--well-radius) * var(--corner-scale));    corner-shape: var(--corner-shape);    background: var(--color-surface-inset);    box-shadow: var(--shadow-pressed);    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }  /* A tray's corner is the sheet's corner plus its 4px inset, so the gap keeps one width around the bend. */  .tray {    --surface-color: var(--color-surface-tray);    position: relative;    min-width: 0;    padding: 4px;    border-radius: calc(var(--radius-surface) * var(--corner-scale) + 4px);    corner-shape: var(--corner-shape);    background: var(--color-surface-tray);    color: var(--color-text-body);    /* Recessed by tone and a hairline; it holds raised sheets, so it casts no shadow of its own. */    box-shadow: inset 0 0 0 .5px var(--color-line-hair), inset 0 1px 2px -1px var(--color-shade-2);    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }}