Surfaces compose existing design decisions into a container treatment. A Surface brings together padding, corner shape, background, foreground, border and shadow. Components choose the role; the theme owns the shared appearance and responsive behavior. A Surface does not define layout, document semantics, interaction logic or z-index.
{
"breakpoints": { "xs": 0, "md": 768 },
"surfaces": {
"contained": {
"padding": "density(2)",
"radius": "radius(2)",
"bg": "palette(surface.main)",
"color": "palette(surface.contrast)",
"border": "border(1)",
"shadow": "xs(shadow(1)) md(shadow(3))"
}
}
}This excerpt assumes referenced Density, Radius, Palette, Border and Shadow tokens exist in the effective theme. The six supported fields are padding, radius, bg, color, border and shadow. Each accepts a nonempty CSS string or a responsive progression with a base value.
Built-in roles are contained, outlined and flat. Partial overrides inherit missing fields from that role's defaults. A new custom role inherits the contained defaults for missing fields. A supplied field replaces the entire inherited expression; breakpoint fragments are not merged.
.card {
@ds-surface(contained);
}.card {
padding: var(--uxdsl__surface__contained-padding);
border-radius: var(--uxdsl__surface__contained-radius);
background: var(--uxdsl__surface__contained-bg);
color: var(--uxdsl__surface__contained-color);
border: var(--uxdsl__surface__contained-border);
box-shadow: var(--uxdsl__surface__contained-shadow);
}The generated theme variables retain references such as var(--uxdsl__density__2) and var(--uxdsl__radius__2). In this example, the shadow changes from shadow-1 to shadow-3 at 768px and remains there until another rule overrides it. Density and Radius can also respond through their own mappings. The component does not need to repeat those decisions.
generateThemeCss(nextTheme)..special-card {
@ds-surface(contained);
box-shadow: none; /* Intentional local exception */
}Preserve the shared role when a component belongs to the system. Copying its current padding, color and shadow into local CSS discards future changes to the role.
.notice {
@ds-surface(outlined primary 2);
}The optional tone names a Palette family. Outlined uses a transparent background with that family's main color for text and a 1px solid border. Flat uses transparent background and the main foreground while keeping the configured border. Contained and custom roles use the family's main background and contrast foreground while keeping their configured border.
The numeric size overrides padding with density(n) and corners with radius(n). Inspect both definitions before using it; it does not mean n pixels and the two scales need not match. Without these optional arguments, all six fields follow the Surface. A tone intentionally overrides some fields, so later edits to those overridden fields will not affect that toned component.
PostCSS, runtime theme generation, the inspector and this demo share the Surface engine. Defaults are maintained there and generated into postcss-uxdsl/theme/default-surfaces.uxdsl. A legacy @theme Surface pack remains valid in the same compilation:
@theme {
surface-contained: {
padding: density(2);
radius: radius(2);
bg: palette(surface-main);
color: palette(surface-contrast);
border: border(1);
shadow: shadow(1);
}
}JSON fields override matching legacy fields, which override shared defaults. Include legacy definitions in each build that needs them: Surface packs no longer leak through a process-global cache. Unknown roles, fields and referenced Radius/Border/Shadow presets produce errors; verify other token dependencies and actual CSS too. The compiler is not a full CSS or accessibility validator.
The preview reads the active theme and shared defaults. Its JSON editor applies scoped browser changes without saving your source file. Resize the actual browser to inspect responsive behavior. Invalid edits preserve the last valid preview; Reset restores the active theme. The optional tone and size controls use the same composition function as the compiler. Size options come from the effective Density and Radius definitions, including shared defaults.
Actual viewport: 0px. Resize the browser to test transitions. The preview uses the active theme and shared engine defaults. Edits below are scoped to this demo and do not save source JSON.
A shared container treatment.
.card { @ds-surface(contained); }{
"--uxdsl__surface__contained-padding": "var(--uxdsl__density__2)",
"--uxdsl__surface__contained-radius": "var(--uxdsl__radius__2)",
"--uxdsl__surface__contained-bg": "var(--uxdsl__palette__surface-main)",
"--uxdsl__surface__contained-color": "var(--uxdsl__palette__surface-contrast)",
"--uxdsl__surface__contained-border": "1px solid var(--uxdsl__palette__surface-dark)",
"--uxdsl__surface__contained-shadow": "var(--uxdsl__shadow__1)"
}A shared container treatment.
.card { @ds-surface(outlined); }{
"--uxdsl__surface__outlined-padding": "var(--uxdsl__density__2)",
"--uxdsl__surface__outlined-radius": "var(--uxdsl__radius__2)",
"--uxdsl__surface__outlined-bg": "transparent",
"--uxdsl__surface__outlined-color": "var(--uxdsl__palette__surface-contrast)",
"--uxdsl__surface__outlined-border": "1px solid var(--uxdsl__palette__neutral-main)",
"--uxdsl__surface__outlined-shadow": "none"
}A shared container treatment.
.card { @ds-surface(flat); }{
"--uxdsl__surface__flat-padding": "var(--uxdsl__density__2)",
"--uxdsl__surface__flat-radius": "var(--uxdsl__radius__2)",
"--uxdsl__surface__flat-bg": "transparent",
"--uxdsl__surface__flat-color": "var(--uxdsl__palette__surface-contrast)",
"--uxdsl__surface__flat-border": "none",
"--uxdsl__surface__flat-shadow": "none"
}AI implementation guide
Responsibility: maintain shared container treatments by composing existing design-system roles and tokens. Preserve intent, not just the current computed value.
surfaces configuration, legacy imports, breakpoints and referenced Density, Radii, Palette, Borders and Shadows before selecting a role.@ds-surface(role). Do not rebuild its six properties locally merely because the result looks identical today.density(n) and radius(n). Do not infer measurements from the numeric key.Decision rule: Choose the container role in the component. Compose its shared visual decisions in the theme. Use optional arguments or native CSS only for deliberate overrides.
For “make all contained cards less elevated on desktop,” inspect the contained role and its consumers, modify that role's shadow progression, apply the theme and verify each relevant breakpoint. Keep components using @ds-surface(contained). For one exceptional card, retain the role and override its shadow locally.