Aspekt logoAspekt UI

Surfaces explains Aspekt depth, contextual surface inheritance, overlay lift, and neutral state treatments.

Depth before decoration

Surfaces are Aspekt's neutral z-axis. The scale runs from surface-1 through surface-8, where each step describes a plane moving toward the viewer rather than a louder visual priority.

Components read the nearest surface context and can lift from it. That keeps popovers, dropdowns, dialogs, drawers, and toasts visibly separated even when they open inside another elevated component.

Tokens, classes, and API

Surface tokens and class names are styling tools. The React API is what calculates the current surface level and passes that level to nested components.

Tokens

--surface-1, --surface-current, --surface-hover

CSS custom properties that define colors, shadows, and contextual treatments. Tokens do not choose a level by themselves.

Class names

bg-surface-current, border-border, shadow-surface-3

Tailwind utilities mapped to the tokens. Use these when styling markup inside an already resolved surface.

React API

SurfaceProvider value, Surface level, Surface lift

Components that resolve the current level, publish it to descendants, and update contextual tokens for the subtree.

Component props

surface, surfaceLift, surfaceShadow

Built-in surface controls on Aspekt components. Most overlays already lift from their current context by default.

How the model works

A surface sets --surface-current for its subtree. Muted, hover, active, border, and ring treatments are then mixed from that current substrate, so neutral UI states keep the right contrast at every depth.

Choose a substrate

Use surface-1 for the page or application canvas, then reserve higher levels for content that visually moves forward.

Lift from context

Use lift when a component should sit above whatever surface contains it. Nested overlays stay separated without hard-coded colors.

Keep meaning separate

Use neutral surfaces for depth. Use action, success, warning, destructive, and info colors only when the UI needs semantic meaning.

Move through levels

Drag both knobs to choose which slice of the ladder to nest. Each layer lifts a single step off the one it sits in, whether the stack spans two levels or all eight.

1 - 8

One card, many substrates

Move the card through the ladder. The neutral buttons, muted regions, borders, and lifted menu all recompute from that card's current surface instead of using fixed colors.

Workspace review

Card substrate surface-3

Pending edits12
Nested menusurface-5
surface-3

Dashboard, drawer, popover

A common product flow: open a drawer from a dashboard, then open a popover inside that drawer. Each overlay lifts from the surface it actually sits in.

Revenue dashboard

Surface-1 application background

Pipeline

Conversion

Expansion

Segment drawer

Lifted to surface-4 from the dashboard

4

Account owner

Mina Torres

Status filter

Status popover

Lifted from the drawer context to surface-6.

API shape

The exported surface primitives are small by design. Most product code should set an explicit level at major app boundaries, then let relative lift calculate nested panels and overlays from the current context.

The calculation is automatic when you use Surface,ElevatedSurface, or a component with surfaceLift. A class like bg-surface-current only reads the already resolved token.

SurfaceProvider
value={1}

Sets the current surface context for descendants. Values are rounded and clamped from 1 to 8.

Surface
level={2}lift={1}shadow={false}

Creates a plane, resolves absolute or relative depth, applies the matching surface background and shadow, then publishes the resolved level.

ElevatedSurface
lift={1}

A convenience wrapper around Surface for the common case where something should lift one step from its parent.

Component props
surfacesurfaceLiftsurfaceShadow

Surface controls built into overlays and framed primitives. Use them when the component should override its default depth.

Built-in component surfaces

Components that create a new plane already participate in the surface model. Override surface, surfaceLift, or surfaceShadow only when a product flow needs a different depth.

Small overlayssurfaceLift={2}
PopoverContentSelectContentComboboxContent

Small overlays lift two levels above the surface that opened them.

Prominent overlayssurfaceLift={4}
DialogContentDrawerContentToast

Prominent overlays move further forward from the application canvas or current overlay.

Framed contentoutline lifts by 1
CardTableScrollAreaSidebarInsetContent

Framed content surfaces lift one level for outline variants and stay in context for softer variants.

Next.js placement

In the App Router, set the base context once near the root layout, then let pages and overlays choose absolute levels or relative lift based on the plane they introduce.

import type { ReactNode } from "react"; import { SurfaceProvider } from "@/components/aspekt/surface";import "@/app/globals.css"; export default function RootLayout({ children }: { children: ReactNode }) {  return (    <html lang="en">      <body className="bg-surface-current text-primary">        <SurfaceProvider value={1}>{children}</SurfaceProvider>      </body>    </html>  );}

Surface levels

--surface-1

The default app and site substrate.

--surface-2

Cards, panels, tables, and persistent content surfaces.

--surface-3

Small overlays, menus, and elevated nested panels.

--surface-4

Dialogs, drawers, toasts, and prominent overlay surfaces.

--surface-5

Dropdowns and popovers opened inside larger overlays.

--surface-6

Second-order nested overlays that still need separation.

--surface-7

Rare foreground planes above already elevated content.

--surface-8

The clamped top of the surface ladder.

Surface treatments

--surface-current

The current substrate inherited from the nearest Surface context.

--surface-muted

Soft fills, quiet regions, rails, code blocks, and inactive controls.

--surface-hover

Hover and transient interaction fills inside a surface.

--surface-active

Pressed, selected, checked, and active neutral states.

--surface-border

Default contextual borders and separators.

--surface-border-strong

Higher contrast borders for selected or emphasized outlines.

--surface-ring

Contextual focus rings and active outlines.

Surface shadows

shadow-surface-1

The resting shadow for the app canvas and base substrate.

shadow-surface-2

A quiet elevation for cards, panels, and tables.

shadow-surface-3

A visible lift for floating panels and compact overlays.

shadow-surface-4

The default overlay shadow for dialogs, drawers, and toasts.

shadow-surface-5

A stronger shadow for overlays opened inside overlays.

shadow-surface-6

A high foreground shadow for second-order nested content.

shadow-surface-7

A rare foreground shadow above already elevated planes.

shadow-surface-8

The strongest clamped shadow in the surface scale.