Surface System
Context-aware surface nesting, shadows, and app integration.
The surface system is how Cubitt keeps nested UI from flattening into the same background. Consumers declare the surface level only where the app paints its own boundary. Cubitt components then move relative to that level and pass the computed level to their children.
Surface Levels
Use odd-numbered levels for major surfaces and even-numbered levels for interaction states.
| Level | Role |
|---|---|
surface-1 | Main product inset or full-screen workspace |
surface-2 | Hover, focus, or row state on surface-1 |
surface-3 | App shell, sidebar, page chrome, or card above surface-1 |
surface-4 | Hover, focus, or row state on surface-3 |
surface-5 | Popover, panel, or floating content above surface-3 |
surface-6 | Hover, focus, or row state on surface-5 |
surface-7 | Menu or deeply nested floating content |
surface-8 | Hover, focus, or row state on surface-7 |
Ownership Model
Consumers set surface context only at app-owned painted boundaries. If the app
shell is built with Cubitt's sidebar components, SidebarProvider is that
painted boundary and owns the shell context. It defaults to shell surface-3
in light mode and shell surface-2 in dark mode. SidebarInset defaults to
surface-1 in both modes. Custom app shells should still pair their painted
background with SurfaceProvider.
| Boundary | Consumer responsibility |
|---|---|
| Cubitt sidebar app shell | Use SidebarProvider; pass surfaceLevel only when the shell should use a non-default level or theme mapping |
| Custom app root or shell | Pair the painted bg-surface-* class with SurfaceProvider level={n} |
| App-owned inset or workspace reset | Add a nested provider only when the app paints a new root surface |
| Custom app-owned panel | Use SurfaceProvider or Surface if the custom panel contains Cubitt components |
| Cubitt-owned components | Do not add a provider; Cubitt advances or resets context internally |
import {
SidebarInset,
SidebarProvider,
} from "@tilt-legal/cubitt-components/sidebar";
export function SidebarAppShell({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>
);
}import { SurfaceProvider } from "@tilt-legal/cubitt-components/utilities";
export function CustomAppShell({ children }: { children: React.ReactNode }) {
return (
<SurfaceProvider level={3}>
<div className="min-h-screen bg-surface-3">
<aside>App chrome</aside>
<main className="bg-surface-1">
<SurfaceProvider level={1}>{children}</SurfaceProvider>
</main>
</div>
</SurfaceProvider>
);
}SurfaceProvider does not paint a background. It tells Cubitt components which
surface has already been painted by the app. SidebarProvider is the exception:
it paints the sidebar shell surface and provides that context for the app shell.
Cubitt-Owned Surfaces
Do not wrap normal Cubitt nesting with extra providers. If a Cubitt component owns and paints a container, it also owns the nested surface context.
For example, a Card inside a surface-1 workspace lands on surface-3 and provides surface-3 context to its children. A secondary button inside that card then lands on surface-4 without any consumer override.
import { Button } from "@tilt-legal/cubitt-components/button";
import { Card } from "@tilt-legal/cubitt-components/card";
<Card>
<Button variant="secondary">Secondary action</Button>
</Card>;The same rule applies to floating surfaces. Popovers, menus, sheets, selection toolbars, sidebars, and sidebar insets handle their own context when they paint a surface.
Custom App Surfaces
Use Surface when an app-owned custom container should both paint the next surface and provide that new level to its children.
import { Button } from "@tilt-legal/cubitt-components/button";
import { Surface } from "@tilt-legal/cubitt-components/utilities";
<Surface offset={2} className="rounded-xl p-4">
<Button variant="secondary">Nested action</Button>
</Surface>;offset={2} means parent + 2. Surface paints that computed surface-* background, adds the next border-* half-stop edge, then provides the computed surface level to nested Cubitt components.
Borders And Shadows
Surface borders and shadows both derive from surface levels, but they do different jobs.
| Token | Use |
|---|---|
border-border-1 to border-border-8 | 1px component edges and built-in divider lines. Component edges use the next stop; internal separators use two stops above the current surface. |
shadow-surface-1 to shadow-surface-8 | Drop shadows only, matched to the floating surface level |
Use borders for in-flow containers such as cards, panels, controls, and internal separators. Use shadows only when a surface floats over other content, such as popovers, menus, sheets, and toolbars.
Frosted Utilities
Frosted utilities combine a semi-transparent surface background with backdrop blur. Components can set the material surface from the active surface level; standalone usage falls back to surface-2.
<div className="material-medium rounded-lg px-4 py-2">
<h2>Frosted glass card</h2>
<p>Content with backdrop blur</p>
</div>| Class | Opacity | Blur | Use |
|---|---|---|---|
material-ultra-thick | 84% | 30px | Strong glass separation |
material-thick | 72% | 30px | Navigation bars and prominent glass cards |
material-medium | 60% | 30px | Default frosted cards and controls |
material-thin | 48% | 30px | Subtle overlays |
material-ultra-thin | 36% | 30px | Minimal blur |