Skip to main content

Surface System

23 background tokens and 19 foreground tokens covering every UI surface need. Named by purpose — page, section, panel, card, header, row, control, popup, overlay, tooltip, accent, inverse, and semantic status. Every token works correctly across light, dark, and glass modes.

Quick start
// Background tokens — named by purpose
// --md-surface-page          root canvas (body/html)
// --md-surface-section       app shell, sidebar, topbar, nav
// --md-surface-panel         grouped region, settings group, dashboard zone
// --md-surface-card          card, article, list container
// --md-surface-header        table head, card header, accordion header
// --md-surface-row           list/table rows
// --md-surface-row-alt       zebra stripe
// --md-surface-control       input fields, select, textarea
// --md-surface-control-track switch/slider/progress track
// --md-surface-overlay       dropdown, menu, date picker (anchored float)
// --md-surface-popup         dialog, drawer, bottom sheet (ALWAYS OPAQUE)
// --md-surface-tooltip       small hint (inverted contrast)
// --md-surface-accent        primary-branded panel
// --md-surface-inverse       snackbar, high-contrast chip
// --md-surface-error/success/warning/info

// Paired foregrounds:
// --md-on-{surface-name}     e.g. --md-on-card, --md-on-popup, --md-on-tooltip
// --md-on-surface-muted      secondary text (works on any neutral surface)
// --md-on-surface-disabled   disabled text
// --md-on-surface-placeholder placeholder text

Layout Hierarchy

import { html, renderApp } from 'mates';
import { Text } from 'mates-ui';

const App = () => () => html`
<div style="background:var(--md-surface-page);border-radius:var(--md-radius-lg);padding:1rem;border:0.06rem solid var(--md-color-border);max-width:32rem;width:100%;">
${Text("--md-surface-page", { type: "label-sm", color: "muted" })}
${Text("Root canvas — body / html", { type: "body-sm" })}
<div style="background:var(--md-surface-section);border-radius:var(--md-radius-lg);padding:1rem;border:0.06rem solid var(--md-color-border);margin-top:0.75rem;">
${Text("--md-surface-section", { type: "label-sm", color: "muted" })}
${Text("App shell — sidebar, topbar, nav", { type: "body-sm" })}
<div style="background:var(--md-surface-panel);border-radius:var(--md-radius-lg);padding:1rem;border:0.06rem solid var(--md-color-border);margin-top:0.75rem;">
${Text("--md-surface-panel", { type: "label-sm", color: "muted" })}
${Text("Grouped region — dashboard zone", { type: "body-sm" })}
<div style="background:var(--md-surface-card);border-radius:var(--md-radius-lg);padding:1rem;border:0.06rem solid var(--md-color-border);box-shadow:var(--md-shadow-sm);margin-top:0.75rem;">
${Text("--md-surface-card", { type: "label-sm", color: "muted" })}
${Text("Card — article, list container", { type: "body-sm" })}
</div>
</div>
</div>
</div>
`;

renderApp(App, document.getElementById('app'));

Within a Card

import { html, renderApp } from 'mates';

const ROWS = [
{ bg: "var(--md-surface-header)", label: "--md-surface-header", text: "table head / accordion header", border: true },
{ bg: "var(--md-surface-row)", label: "--md-surface-row", text: "list / table rows", border: true },
{ bg: "var(--md-surface-row-alt)", label: "--md-surface-row-alt", text: "zebra stripe", border: true },
{ bg: "var(--md-surface-control)", label: "--md-surface-control", text: "input, select, textarea", border: true },
{ bg: "var(--md-surface-control-track)", label: "--md-surface-control-track", text: "slider / switch track", border: false },
];

const App = () => () => html`
<div style="background:var(--md-surface-card);border-radius:var(--md-radius-lg);border:0.06rem solid var(--md-color-border);overflow:hidden;max-width:36rem;width:100%;">
${ROWS.map(({ bg, label, text, border }) => html`
<div style="background:${bg};padding:var(--md-space-3) var(--md-space-4);
${border ? 'border-bottom:0.06rem solid var(--md-color-border);' : ''}
display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:var(--md-text-sm);color:var(--md-on-card);">${text}</span>
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-surface-muted);">${label}</code>
</div>
`)}
</div>
`;

renderApp(App, document.getElementById('app'));

Interactive States

import { html, renderApp } from 'mates';

const STATES = [
{ bg: "var(--md-surface-row)", label: "--md-surface-row", text: "Normal row", border: true },
{ bg: "var(--md-surface-hover)", label: "--md-surface-hover", text: "Hover state", border: true },
{ bg: "var(--md-surface-selected)", label: "--md-surface-selected", text: "Selected state", border: true },
{ bg: "var(--md-surface-pressed)", label: "--md-surface-pressed", text: "Pressed state", border: false },
];

const App = () => () => html`
<div style="background:var(--md-surface-card);border-radius:var(--md-radius-lg);border:0.06rem solid var(--md-color-border);overflow:hidden;max-width:36rem;width:100%;">
${STATES.map(({ bg, label, text, border }) => html`
<div style="background:${bg};padding:var(--md-space-3) var(--md-space-4);
${border ? 'border-bottom:0.06rem solid var(--md-color-border);' : ''}
display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:var(--md-text-sm);color:var(--md-on-card);">${text}</span>
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-surface-muted);">${label}</code>
</div>
`)}
</div>
`;

renderApp(App, document.getElementById('app'));

Floating Surfaces

import { html, renderApp } from 'mates';
import { FilledButton, TextButton } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-4)" wrap align="flex-start">
<div style="flex:1;min-width:11rem;background:var(--md-surface-overlay);border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);padding:var(--md-space-4);box-shadow:var(--md-shadow-md);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-surface-muted);display:block;margin-bottom:var(--md-space-2);">--md-surface-overlay</code>
<div style="font-size:var(--md-text-sm);color:var(--md-on-card);margin-bottom:var(--md-space-3);">dropdown / menu</div>
<div style="display:flex;flex-direction:column;gap:var(--md-space-1);">
<div style="padding:var(--md-space-1) var(--md-space-2);border-radius:var(--md-radius-xs);background:var(--md-surface-hover);font-size:var(--md-text-sm);color:var(--md-on-card);">Menu item</div>
<div style="padding:var(--md-space-1) var(--md-space-2);border-radius:var(--md-radius-xs);font-size:var(--md-text-sm);color:var(--md-on-card);">Menu item</div>
<div style="padding:var(--md-space-1) var(--md-space-2);border-radius:var(--md-radius-xs);font-size:var(--md-text-sm);color:var(--md-on-card);">Menu item</div>
</div>
</div>
<div style="flex:1;min-width:11rem;background:var(--md-surface-popup);border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-lg);padding:var(--md-space-4);box-shadow:var(--md-shadow-dialog);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-surface-muted);display:block;margin-bottom:var(--md-space-2);">--md-surface-popup</code>
<div style="font-size:var(--md-text-sm);color:var(--md-on-popup);margin-bottom:var(--md-space-3);">dialog / drawer (always opaque)</div>
<div style="display:flex;gap:var(--md-space-2);justify-content:flex-end;">
${TextButton("Cancel", { size: "sm" })}
${FilledButton("OK", { size: "sm" })}
</div>
</div>
<div style="flex:1;min-width:10rem;">
<div style="background:var(--md-surface-tooltip);border-radius:var(--md-radius-sm);padding:var(--md-space-2) var(--md-space-3);box-shadow:var(--md-shadow-sm);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-tooltip);opacity:0.75;display:block;margin-bottom:0.25rem;">--md-surface-tooltip</code>
<div style="font-size:var(--md-text-sm);color:var(--md-on-tooltip);">tooltip (inverted)</div>
</div>
</div>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Semantic Surfaces

import { html, renderApp } from 'mates';

const SEMANTIC = [
{ bg: "var(--md-surface-error)", fg: "var(--md-on-error)", label: "--md-surface-error", text: "Something went wrong" },
{ bg: "var(--md-surface-success)", fg: "var(--md-on-success)", label: "--md-surface-success", text: "Operation complete" },
{ bg: "var(--md-surface-warning)", fg: "var(--md-on-warning)", label: "--md-surface-warning", text: "Please review this" },
{ bg: "var(--md-surface-info)", fg: "var(--md-on-info)", label: "--md-surface-info", text: "Here is some context" },
];

const App = () => () => html`
<x-row gap="var(--md-space-3)" wrap>
${SEMANTIC.map(({ bg, fg, label, text }) => html`
<div style="flex:1;min-width:10rem;background:${bg};border-radius:var(--md-radius-md);padding:var(--md-space-4);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:${fg};opacity:0.7;display:block;margin-bottom:var(--md-space-1);">${label}</code>
<div style="font-size:var(--md-text-sm);font-weight:600;color:${fg};">${text}</div>
</div>
`)}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Accent & Inverse

import { html, renderApp } from 'mates';

const App = () => () => html`
<x-row gap="var(--md-space-4)" wrap>
<div style="flex:1;min-width:14rem;background:var(--md-surface-accent);border-radius:var(--md-radius-lg);padding:var(--md-space-5);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-accent);opacity:0.7;display:block;margin-bottom:var(--md-space-2);">--md-surface-accent</code>
<div style="font-size:var(--md-text-base);font-weight:600;color:var(--md-on-accent);margin-bottom:var(--md-space-1);">Primary-branded</div>
<div style="font-size:var(--md-text-sm);color:var(--md-on-accent);opacity:0.8;">Active nav item, highlight panel, featured zone</div>
</div>
<div style="flex:1;min-width:14rem;background:var(--md-surface-inverse);border-radius:var(--md-radius-lg);padding:var(--md-space-5);">
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-inverse);opacity:0.7;display:block;margin-bottom:var(--md-space-2);">--md-surface-inverse</code>
<div style="font-size:var(--md-text-base);font-weight:600;color:var(--md-on-inverse);margin-bottom:var(--md-space-1);">Snackbar / high-contrast chip</div>
<div style="font-size:var(--md-text-sm);color:var(--md-on-inverse);opacity:0.8;">Dark-on-light / light-on-dark flip</div>
</div>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Foreground Tokens

import { html, renderApp } from 'mates';
import { Text } from 'mates-ui';

const TOKENS = [
{ token: "--md-on-card", label: "on-card", text: "Primary text on any neutral surface" },
{ token: "--md-on-surface-muted", label: "on-surface-muted", text: "Supporting / secondary text" },
{ token: "--md-on-surface-placeholder", label: "on-surface-placeholder", text: "Placeholder text" },
{ token: "--md-on-surface-disabled", label: "on-surface-disabled", text: "Disabled text" },
];

const App = () => () => html`
<x-col gap="var(--md-space-3)" style="max-width:32rem;width:100%;">
${TOKENS.map(({ token, label, text }) => html`
<div style="display:flex;justify-content:space-between;align-items:center;padding:var(--md-space-2) var(--md-space-3);background:var(--md-surface-card);border-radius:var(--md-radius-sm);border:0.06rem solid var(--md-color-border);">
<span style="font-size:var(--md-text-sm);color:${token};">${text}</span>
<code style="font-size:0.63rem;font-family:var(--md-font-mono);color:var(--md-on-surface-muted);">--md-${label}</code>
</div>
`)}
</x-col>
`;

renderApp(App, document.getElementById('app'));