UX-DSL
XS0px

Surfaces

Shared container roles composed from your design system.

Give containers a shared visual role.

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.

Configure the role in JSON

{
  "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.

Component intent

.card {
  @ds-surface(contained);
}

CSS consumption

.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.

Choose the scope of your change

  • One container: select another configured Surface or add an intentional CSS override after the directive.
  • Every consumer of a Surface: edit its definition, then rebuild or apply the theme with generateThemeCss(nextTheme).
  • A foundational decision: change Density, Radius, Palette, Border or Shadow only when all direct and linked consumers should follow.
.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.

Optional tone and size

.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.

One engine and a supported legacy input

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.

Interactive Demo

Shared engine playground

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.

Surface presets

contained

A shared container treatment.

.card { @ds-surface(contained); }
Resolved preset references
{
  "--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)"
}

outlined

A shared container treatment.

.card { @ds-surface(outlined); }
Resolved preset references
{
  "--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"
}

flat

A shared container treatment.

.card { @ds-surface(flat); }
Resolved preset references
{
  "--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

How an AI agent should use Surfaces

Responsibility: maintain shared container treatments by composing existing design-system roles and tokens. Preserve intent, not just the current computed value.

  • Inspect the effective surfaces configuration, legacy imports, breakpoints and referenced Density, Radii, Palette, Borders and Shadows before selecting a role.
  • Reuse a suitable configured role with @ds-surface(role). Do not rebuild its six properties locally merely because the result looks identical today.
  • Preserve token references. Surface radius references Radius, not Spacing; the Surface consumes that system's shape behavior.
  • Use a tone only when an explicit Palette-family override is intended. Check which fields it overrides for the selected variant.
  • Use the size argument only after checking both density(n) and radius(n). Do not infer measurements from the numeric key.
  • Change a shared Surface only when all its consumers should follow. Use a different role or subsequent local CSS for an intentional exception.
  • Partial overrides inherit missing fields; replacing a field replaces its full responsive expression. Custom roles inherit contained defaults.
  • Define roles and dependencies before use. Keep semantic HTML, layout and interaction behavior appropriate to the component.
  • Use the shared generator and inspector; update source configuration instead of generated CSS or demo-only default maps.
  • Verify breakpoint boundaries, intermediate persistence, nested containers, tone overrides, wrapping, border sizing, clipping, foreground contrast, focus visibility and all shared consumers.

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.

Agent reasoning example

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.