From harness-claude
Applies Gestalt figure-ground principle for UI depth in modals, dialogs, overlays, cards using shadows, contrast, elevation, and z-index strategies. Fixes flat designs and stacking issues.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Depth perception — distinguishing foreground from background, ambiguous figure-ground as a design tool, z-axis ordering, overlay and modal perception
Guides CSS box-shadow usage for semantic UI elevation in components like cards, modals, dropdowns; covers shadow anatomy, multi-layer techniques, dark mode, chromatic shadows.
Enforces WCAG 2.1 AA compliant web UIs using Tailwind, glassmorphism, dark mode. Ensures color contrast, visible buttons/borders, focus states, touch targets.
Provides practical UI design patterns and principles from Refactoring UI and Practical UI for layout, spacing, typography, color, hierarchy, and styling in web projects.
Share bugs, ideas, or general feedback.
Depth perception — distinguishing foreground from background, ambiguous figure-ground as a design tool, z-axis ordering, overlay and modal perception
Understand the law. The Gestalt principle of figure-ground states that the brain automatically separates visual input into a foreground figure (the object of attention) and a background ground (the surrounding context). This separation is not optional — the brain performs it on every visual scene. Design exploits this by controlling which elements read as "figure" (foreground, interactive, primary) and which read as "ground" (background, contextual, secondary).
The five cues that establish figure-ground:
Create depth through elevation and shadow. The primary mechanism for establishing figure-ground in digital interfaces is the shadow. A shadow beneath an element signals "this element is lifted above the surface, closer to the viewer."
Worked example — Material Design's elevation system: Material Design codifies figure-ground as literal elevation measured in dp (density-independent pixels):
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08))box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06))box-shadow: 0 8px 16px rgba(0,0,0,0.12), 0 4px 6px rgba(0,0,0,0.07))box-shadow: 0 16px 24px rgba(0,0,0,0.14), 0 6px 8px rgba(0,0,0,0.1))box-shadow: 0 24px 38px rgba(0,0,0,0.14), 0 9px 46px rgba(0,0,0,0.12))Each level creates a stronger figure-ground separation. The user intuitively understands: higher shadow = closer to me = more important right now.
Decision procedure: Assign each UI surface to one elevation level. Interactive overlays (menus, popovers, modals) must always be at a higher elevation than the content they overlay. Never place two overlapping surfaces at the same elevation — this creates ambiguous figure-ground.
Use scrim/overlay to reinforce modal figure-ground. When a modal or dialog appears, the content behind it must be visually demoted to "ground." The scrim (semi-transparent overlay) accomplishes this by reducing the contrast, color saturation, and clarity of the background content.
Worked example — Stripe's modal pattern:
background: rgba(0, 0, 0, 0.4) — dims the page to 60% brightnessborder-radius: 12px, shadow at Material 24dp equivalentWorked example — Apple's iOS sheet presentation:
Apply figure-ground to card design. Cards are the most common figure-ground pattern in web design. A card is a figure that lifts off the page ground.
Worked example — Stripe's pricing cards:
#f6f9fc (light blue-gray) — this is the ground0 2px 4px rgba(0,0,0,0.08)) — these are the figuresrgba(50, 50, 93, 0.1)) rather than pure black — this is more natural (real-world shadows pick up ambient color) and less harshDecision procedure for card backgrounds:
0 1px 3px rgba(0,0,0,0.1). Background tint: #f5f5f5 to #f0f4f8.0 1px 2px rgba(0,0,0,0.05)).#1e1e1e, Ground: #121212. Shadow is nearly invisible in dark mode — rely on background contrast instead.Handle figure-ground in dark mode. Dark mode inverts the typical figure-ground relationship. In light mode, shadows push elements upward. In dark mode, shadows are invisible against dark backgrounds. Material Design solves this by using surface lightness as the elevation signal:
Material Design dark mode elevation:
#121212#1e1e1e (overlaid with 5% white)#232323 (overlaid with 9% white)#2c2c2c (overlaid with 12% white)#333333 (overlaid with 15% white)#383838 (overlaid with 16% white)Higher = lighter. The brain interprets lighter surfaces as closer (figure) and darker surfaces as farther (ground). This maintains the figure-ground hierarchy without any shadows.
Exploit ambiguous figure-ground deliberately. In rare cases, ambiguous figure-ground creates visual interest. The classic example is Rubin's vase — the same image can be seen as a vase (figure) or two faces (ground), depending on the viewer's focus.
Worked example — negative space logos:
When to use ambiguity in interfaces: Almost never. Interface design demands unambiguous figure-ground so users know what is interactive (figure) and what is context (ground). Ambiguity belongs in branding and illustration, not in functional UI.
Figure-ground in CSS is managed through z-index, and mismanaged z-index is the most common implementation failure of figure-ground design.
Define a z-index scale (not arbitrary values):
| Level | z-index | Usage |
|---|---|---|
| Base | 0 | Page content, ground |
| Raised | 10 | Cards, sticky headers |
| Dropdown | 100 | Dropdowns, popovers, tooltips |
| Overlay | 200 | Scrims, sidebar overlays |
| Modal | 300 | Modals, dialogs |
| Toast | 400 | Toast notifications, snackbars |
| Maximum | 500 | Development tools, debug overlays |
Never use z-index values like 9999, 99999, or 2147483647. These indicate a z-index arms race where developers are fighting stacking context conflicts by escalating values. The fix is a defined scale — every surface has one correct level.
Stacking context isolation: Use isolation: isolate on component root elements to create new stacking contexts. This prevents a z-index inside one component from competing with z-index in another component. A z-index: 100 inside an isolated component cannot escape to compete with a z-index: 50 in the global context.
Tables present a figure-ground challenge: which rows are figure and which are ground?
#fff / #f9fafb) to create micro figure-ground separation between rows. This improves scan accuracy for wide tables where the eye might drift to an adjacent row.background: #eef2ff (blue tint) on hover signals "this row is selected as figure."background: #e0e7ff plus a left border accent (border-left: 3px solid #4f46e5). The border adds a convexity cue — the row "extends toward the viewer."Complex interfaces have multiple figure-ground layers:
Each layer must maintain unambiguous separation from adjacent layers. If a popover and a card have the same shadow depth, the popover does not feel "above" the card — its figure-ground status is ambiguous.
Flat modals (no scrim, no shadow). A modal that appears as a white box on a white page with no scrim or elevation. The brain cannot determine that this is figure — it reads as "another card appeared" rather than "an overlay is demanding my attention." Fix: always use a scrim (rgba backdrop) plus a shadow that exceeds any other shadow on the page. The modal must be at the highest elevation in the system.
Shadow inflation. Every element has a drop shadow — cards, buttons, inputs, headers, footers. When everything casts a shadow, nothing lifts off the ground. Shadow becomes noise rather than signal. Fix: reserve shadows for true figure-ground separation (cards on a page, menus over content, modals over everything). Flat elements (buttons, inputs, text) should not have shadows in their default state. Stripe uses shadows on exactly two things: cards and modals.
Z-index escalation. Starting with z-index: 1 for a dropdown, then needing z-index: 10 for a modal, then z-index: 9999 for a toast because a third-party widget injected z-index: 1000. Fix: define a z-index scale as a design token. Use stacking context isolation. Audit third-party widgets for rogue z-index values.
Identical elevation on overlapping surfaces. Two overlapping cards with the same shadow — which is on top? The brain cannot resolve this. Fix: any surface that overlaps another must be at a visibly different elevation. If two cards can overlap (e.g., in a draggable UI), the dragged card must gain elevation (increase shadow) to signal "I am above."
Stripe's Blue-Tinted Shadows:
0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3)Material Design Elevation System:
--md-sys-elevation-level1 through --md-sys-elevation-level5Apple's Sheet Presentation:
Vercel's Minimal Elevation:
#fafafa ground, no shadow, 1px border #eaeaeargba(0,0,0,0.5)) + white surface — the only place Vercel uses strong depth cues, making modals feel significantly more elevated by contrast with the normally flat interface