Depth must communicate pressability, insertion or stacking. Flat information stays flat.
Spatial Desktop design system · work in progress
Material follows interaction.
Opaque ABS gives pointer-driven controls a physical body and uses discrete LED lamps for state. Edge-lit acrylic carries temporary, ambient and system information through illuminated engraving. Neither material is decoration: each one explains how an element behaves before it is used.
Philosophy
A small set of rules should explain the whole interface.
Desktop controls echo mouse and keyboard switches. Touch controls belong to the continuous phone-glass surface.
A surface can be raised, level or recessed. Avoid nesting multiple embossed frames around content.
The main content of every app window is always opaque and readable. Glass never sits behind long-form content.
App colour marks focus, selection, progress and an active toggle—not every border and icon.
Buttons move down in 8 ms and release in 16 ms. Toggle buttons click equally fast, then remain physically held.
Two materials, two jobs
Mix them only at a deliberate boundary.
ABS is the body of applications and controls manipulated with a pointer. It should feel like the shell and buttons of a well-made mouse.
- Movable application windows
- Push and toggle buttons on ABS
- Toolbars, tabs and window chrome
- Solid application content surfaces
“Glass” is one continuous acrylic sheet, not a pile of translucent capsules. Neutral parallel reflections reveal its cut thickness; hidden edge emitters feed light into engraved or frosted marks only where state must be communicated.
- Docked Apps, Project and System Areas
- Context menus, popovers and toasts
- Overview and search surfaces
- Small displays inserted into ABS
Light behaves differently
The same colour can communicate state through two different physical models.
ABS uses a restrained state light
Match the real Dolphin controls in the desktop demo: a flat two-pixel application-colour trace with one short halo. It sits flush in the ABS surface, with no contrasting bezel, white centre or extra luminous body.
- Emitter
- Local indicator integrated flush with the control
- Light form
- Flat two-pixel line or three-pixel vertical slit
- App colour
- Application primary colour without whitening
- Bloom
- One restrained six-pixel shadow at 56%
- Inactive
- A faint neutral state rail only where required
Acrylight reveals a path, not a glowing panel
Hidden LEDs inject light at short sections of the cut edge. Total internal reflection keeps the face transparent; only an engraving, frosted groove or locally exposed edge scatters the light toward the user.
- Emitter
- Short concealed injection points along the outer edge
- Thickness
- Neutral parallel reflections reveal the cut panel
- Light path
- Travels invisibly inside the clear sheet
- App colour
- Appears only at injection points and scattering marks
- Bloom
- One sharp core with a four-pixel local scatter
- Inactive
- The etched groove remains faintly visible but unlit
State light is a language
The coloured line is not decoration. Its shape, duration and intensity describe the control state.
neutral · continuous8–14% tint · temporary1–2 px core · persistent| State | Light form | ABS + LED | Edge-lit acrylic | Timing |
|---|---|---|---|---|
| Idle push button | None | Top reflection and contact shadow only | Neutral edge and faint internal reflection only | Continuous |
| Hover | Low contrast lift | Top highlight becomes slightly clearer | Edge becomes slightly clearer; no coloured rail | While hovered |
| Pointer down | No new state light | Moves 2 px; shadow collapses | Stays in the sheet; its inner reflection compresses by 1 px | 8 ms down |
| Toggle on | Persistent state mark | The neutral rail switches to the two-pixel app-colour trace | Edge-fed light scatters from an engraved line | Appears immediately; remains until off |
| Selected tab | Persistent state mark | Pressed key plus the same two-pixel trace used in Dolphin | Selected segment brightens an engraving in the shared sheet | Follows selection |
| Slider progress | Path from minimum to thumb | The completed distance uses the same state trace | Only the completed part of a frosted groove scatters edge light | Tracks pointer directly; no lagging animation |
| Keyboard focus | Temporary state mark | The LED line runs at reduced output | Existing etched boundary brightens locally | Only while focused |
| Disabled | None | Lower contrast, no indicator light | Lower edge contrast, no glow | Until enabled |
Toggle buttons
Pressing and toggling are separate events. Every button performs the same fast 8 ms click. After it resolves, an ABS toggle stays lower and powers its indicator; a glass toggle remains in the shared sheet while its engraved mark stays lit. A push button retains neither.
Sliders
The light represents a value, so it ends exactly at the thumb and follows without easing. ABS reuses the same two-pixel state trace as the active Dolphin tab; the remaining distance is a thin neutral guide. Glass brightens a frosted groove in the acrylic sheet.
Colour ownership
Use the application primary colour for in-app state. System surfaces use their owning Area colour. Warning, error and success colours replace it only when status meaning is more important than app identity.
Where the light may exist
ABS light may appear only as the shared state trace, a small dot, a vertical slit or a backlit symbol—never as glowing plastic. Acrylight may appear only at an outer injection edge or where the shared sheet is engraved, frosted or cut.
Every component in both materials
The behaviour stays consistent. Only the physical response changes.
Push and toggle buttons
Click both versions. Push returns; toggle remains held and shows its material-specific state light.
Opaque keycap · 2 px app-colour trace · no visible housing
One acrylic sheet · etched control boundary · 1 px illuminated engraving
Tabs and segmented choices
The active item is lower and darker in ABS, but optically inset in acrylic glass.
Selected key remains physically down · luminous line shows persistent state
Etched segments stay in one plane · selected groove scatters light
Text and search fields
Both are recessed because they receive content rather than trigger an action.
Opaque well · strong inner shadow · 3 px vertical app-colour slit
Transparent inset · visible edge · restrained focus light
Sliders and continuous values
The active length is meaningful state, so both materials use application colour through different hardware.
2 px application-colour trace · short 6 px halo · neutral remainder
1 px frosted groove · edge-lit progress · transparent acrylic thumb
Menus and popovers
The surrounding surface decides the material, not the menu command itself.
Belongs to the opaque application body
One square sheet · rows remain flat · only the panel owns a cut edge
Rows and selection
Rows are information, not physical keys. Neither material raises every row.
Opaque tint · embedded selection edge
Translucent tint · etched selection edge
Checks and compact choices
A small binary control follows its host surface while preserving the same check state.
Small keycap · pressed cavity when checked
Etched square · illuminated mark when checked
Status and notifications
The message origin decides whether it is embedded in an app or floats at system level.
Solid and connected to its parent window
Floating, transparent and square
Composition rules
How the pieces combine without becoming busy.
Do
- Keep app content fully opaque.
- Use one dominant material per surface.
- Reserve app colour for meaningful state.
- Let empty space separate groups.
- Keep borders thin and local.
- Use real app icons in launch surfaces.
Do not
- Put ABS buttons on a glass sheet.
- Wrap every item in a raised card.
- Give push buttons persistent light.
- Blur text-heavy content over wallpaper.
- Stack border inside border inside border.
- Use glow merely to decorate an edge.
Reference tokens
Recommended defaults. Scale spatial values, not interaction timing.
| Token | Default | Purpose |
|---|---|---|
| --press | 8 ms | Mouse-switch downstroke |
| --release | 16 ms | One-frame release |
| --state-line-size | 2 px | Shared horizontal ABS state trace |
| --abs-live-slit | 3 × 23 px | Vertical identity or live-state marker |
| --abs-led-bloom | 6 px / 56% | Single restrained halo used by the real desktop controls |
| --abs-inactive-guide | 28% | Thin neutral continuation of an unfilled slider |
| --glass-groove | 1 px neutral | Visible but unlit engraving in the shared acrylic sheet |
| --acrylic-injection-edge | Local segments only | Shows where concealed LEDs feed light into the sheet |
| --acrylic-edge-stack | 1 px + 2 px + 3 px inset | Neutral parallel reflections that reveal panel thickness |
| --acrylic-etch-bloom | 1 px core + 4 px / 34% | Sharp engraving with short local optical scatter |
| --acrylic-clean-emission | 0% | Unmarked clear sheet does not visibly emit colour |
| --hover-tint | 3–7% | Temporary interaction preview, never committed state |
| --slider-follow | 0 ms | Progress light must stay attached to the thumb |
| --window-radius | 22 px | Movable app squircle |
| --area-radius | 0 px | Every glass surface and control |
| --inner-radius | 8–14 px | ABS controls and inset wells; glass uses 0 px |
| --edge | 1 px | Neutral material boundary |
| --glass-blur | 20–24 px | Transient and desktop acrylic only |
Implementation templates
Minimal patterns developers can adapt without changing the semantics.
Matching ABS and glass buttons
<div class="surface abs">
<button class="button abs-button">Push action</button>
</div>
<div class="surface glass">
<button class="button glass-button">Push action</button>
</div>
<button class="abs-button toggle-button" aria-pressed="true">
Toggle action
</button>
.abs-button { border-radius: 10px; corner-shape: squircle; }
.glass-button { border-radius: 0; corner-shape: square; }
/* Same ON state; two physically different emitters. */
.abs-button[aria-pressed="true"] .light-bar {
height: 2px;
border-radius: 2px;
background: var(--app-primary);
box-shadow: var(--abs-led-bloom);
}
.glass-button[aria-pressed="true"] .engraving {
height: 1px;
background: color-mix(in srgb, var(--app-primary) 82%, white 18%);
box-shadow: var(--acrylic-etch-bloom);
}
.glass-button:not([aria-pressed="true"]) .engraving {
background: var(--glass-groove); /* visible etch, no emitted colour */
box-shadow: none;
}Matching ABS and glass sliders
<!-- Same value and semantics; different physical rendering. -->
<input class="range abs-range" type="range" min="0" max="100" value="42">
<input class="range glass-range" type="range" min="0" max="100" value="42">
.abs-range .track {
height: 2px;
background: color-mix(in srgb, var(--text) 28%, transparent);
}
.abs-range .filled-part {
height: 2px;
border-radius: 2px;
background: var(--app-primary);
box-shadow: var(--abs-led-bloom);
}
.glass-range .filled-part {
height: 1px;
background: color-mix(in srgb, var(--app-primary) 82%, white 18%);
box-shadow: var(--acrylic-etch-bloom);
}
.glass-range .thumb {
border: 1px solid var(--glass-edge);
border-radius: 0;
corner-shape: square;
background: transparent;
box-shadow: var(--acrylic-edge-stack);
}Glass context menu
<menu class="glass context-menu">
<button class="menu-row">Move to Project</button>
<button class="menu-row">Keep visible</button>
</menu>
.context-menu {
border-radius: 0; /* desktop structure */
background: var(--glass-surface);
border: 1px solid var(--glass-edge);
box-shadow: var(--acrylic-edge-stack);
backdrop-filter: blur(24px) saturate(1.08);
}
.menu-row {
background: transparent; /* rows are not separate glass buttons */
border: 0;
box-shadow: none;
}Solid app with a glass insert
<article class="app-window abs">
<header class="app-titlebar">Dolphin</header>
<div class="glass recessed-field">Downloads</div>
<main class="app-content solid">...files...</main>
</article>
/* The content is always opaque—even when the toolbar contains glass. */
.app-content { background: var(--app-content); opacity: 1; }