Communitygithub.com

nkurunziza-saddy/surfaces

What product UI is made of on the web. It sets elevation levels with exact shadow recipes for light and dark mode, when to use a shadow and when a border, the transparent border for forced-colours mode, a small radius scale with concentric nesting, image outlines, every interactive state as a surface change (hover, pressed, selected, disabled, focus-visible), icons (one set, currentColor, outline and fill, stroke tha…

What is surfaces?

surfaces is a Claude Code agent skill that what product UI is made of on the web. It sets elevation levels with exact shadow recipes for light and dark mode, when to use a shadow and when a border, the transparent border for forced-colours mode, a small radius scale with concentric nesting, image outlines, every interactive state as a surface change (hover, pressed, selected, disabled, focus-visible), icons (one set, currentColor, outline and fill, stroke tha….

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/nkurunziza-saddy/hmm-interfaces/tree/HEAD/surfaces

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

What does surfaces do?

This skill decides what each thing is made of: its fill, edge, shadow, radius, states and icons. It picks from a short list of recipes, so two screens never carry two different cards.

Before any decision, apply product-ui §1 (the taste journal and precedence) and §5 (which skill owns what).

Decide in this order

  1. Is a surface needed? Remove before adding. A heading with space around it needs no card. Never nest a card in a card. Why: every container adds an edge the eye must read.
  2. Which level? Page, raised, floating or overlay. One level per element, picked by what it does, not how much you want it to stand out. elevation.md.
  3. What draws the edge? Elevation gets a shadow ring. Structure gets a border. Never both on one edge. elevation.md.
  4. Which radius? Take the token for the element's role, then make nested radii concentric. elevation.md.
  5. Which states? Design hover, pressed, selected, disabled and focus-visible as changes to the surface, before polishing the default. states-and-icons.md.
  6. Which icon treatment? One set, currentColor, stroke matched to the text. states-and-icons.md.
  7. Is a material allowed? Only floating chrome over scrolling content. materials-and-edges.md.
  8. Edges of the scroll. Dividers, fades, overscroll. materials-and-edges.md.

The four levels

LevelForLightDark
PageThe groundGround fill, no shadowDarkest fill, no shadow
RaisedCards, controls, the chosen optionWhite, ring + 1px drop + 3px tight dropOne step lighter, white ring, tight black drop
FloatingMenus, popovers, tooltips, toastsWhite, ring + 1px drop + 4px tight dropTwo steps lighter, white ring, tight black drop
OverlayDialogs, sheets, command palette (with a scrim)White, ring + 1px drop + 6px tight dropThree steps lighter, white ring, tight black drop

Exact tokens are in elevation.md. The rules behind them:

  • A ring plus a tight drop. Blur ≤ 8px, offset ≤ 6px, drops with a negative spread. Why: big soft blurs read as glow, muddy the page and look generated.
  • Dark mode rises by getting lighter. Shadows barely show on dark, so lightness carries height and a white ring at low alpha carries the edge.
  • Shadows are translucent and one colour. Opaque greys leave a halo. Dark drops stay black.
  • Siblings share a level. Cards in a row have one shadow, one radius, one padding.
  • Hover changes the fill, not the shadow.

Rules that need no reference

  • Borders show structure, shadows show elevation. Dividers, inputs, table cells and selected states keep borders. A card whose border exists only to lift it gets a ring instead.
  • Keep border: 1px solid transparent under a shadow ring. Why: forced-colours mode removes shadows, and the border still draws an edge.
  • Images get a 1px inset outline: outline: 1px solid black at 10% (white at 10% in dark), outline-offset: -1px. Never a tinted colour, which shows as a fringe. Skip logos and transparent artwork.
  • Concentric radius. Outer = inner + padding (+ border), up to 24px of padding. Past it, they are separate surfaces.
  • Focus is a ring, never nothing. :focus-visible shows a 2px accent outline at 2px offset; fields take it flush, as a 2px accent edge with no gap. outline: none without a replacement is a bug. The ring takes no layout space.
  • Disabled controls take no hover, press or scale.
  • Icons: one set per surface, currentColor, outline by default and filled when active. Stroke tracks the neighbouring text weight, 1.5px at 400 up to 2px at 600.
  • Blur-behind only on floating chrome. Never on content, cards or tables.
  • Scrollable overlays (dialogs, drawers, menus) get overscroll-behavior: contain.

Pairs

BrokenFixedWhy
border: 1px solid #e5e5e5 on a cardbox-shadow: var(--elev-raised); border: 1px solid transparentA depth-only border becomes a ring; forced colours still draws an edge
box-shadow: 0 10px 40px rgba(0,0,0,.2)0 0 0 1px a.08, 0 1px 2px a.12, 0 4px 8px -4px a.16Tight and crisp beats big and soft
Dark card with 0 8px 24px #000Lighter fill plus 0 0 0 1px oklch(1 0 0 / .08)Shadows vanish on dark; lightness shows height
Card radius 8, inner image radius 8, padding 8Inner 8, outer 16Concentric corners keep an even gap
<img> melting into a white cardoutline: 1px solid oklch(0 0 0 / .1); outline-offset: -1pxThe edge reads without a border
button:focus { outline: none }button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px }Keyboard users keep their place
1.5px icon stroke beside a 600-weight label2px strokeStroke weight matches stem weight
Filled icons in every toolbar slotOutline by default, filled for the active oneFill is the state signal
backdrop-filter: blur(20px) on a cardAn opaque surfaceBlur belongs to chrome floating over scrolling content

Before you finish

PatternFix
box-shadow with blur > 8px or alpha > .2 on light UIUse a level token
border plus box-shadow doing the same jobRing plus transparent border only
Shadow ring without border: 1px solid transparentAdd it
Same border-radius on a parent and its padded childinner + padding for the parent
outline: none or outline: 0 without :focus-visible replacementAdd the ring
:focus (not :focus-visible) styling a ringSwitch to :focus-visible
border-width changing in a stateColour, fill, or a transparent border of equal width
Hardcoded fill="#…" or stroke="#…" in an iconcurrentColor
Two icon libraries imported on one screenOne
backdrop-filter outside a sticky bar, sidebar or floating menuRemove it
Overlay list without overscroll-behavior: containAdd it
Hard cut at a scroller's edge, or a shadow clipped by overflow: hiddenFade the edge; pad the scroller

Verify by rendering light and dark at 2× and cropping an edge of each level. Hover, press, Tab to focus, select and disable each control and compare the box size before and after. Toggle forced colours (Chrome devtools, rendering emulation) and confirm every card still has an edge.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

nkurunziza-saddy/break

Stress-tests a piece of UI with worst-case data and every reachable state, then reports what broke with a numbered fix for each. Use it when the user types /break, or asks to "break this", "stress-test", "try the worst case", "what happens with long names", "check the empty and error states" or "find the edge cases" on a component or screen.

nkurunziza-saddy/layout

Structure and space in product UI on the web. It sets the spacing scale, grouping, alignment, density (compact or comfortable), hit areas, app shells (sidebar, toolbar, content, inspector), max widths, container-query responsiveness, dropdown and popover sizing, room for long and translated text, layout stability (no shift on load) and sticky elements that must not cover focus.

nkurunziza-saddy/motion

Builds motion for product UI on the web. It decides whether something should move at all, then why, which tool (CSS transition, WAAPI, Motion, spring), which properties, curve, duration, interruption, exit and reduced-motion path, and it writes the code. Also covers stagger and choreography, gestures (drag, flick, sheets), motion tokens and animation performance.

nkurunziza-saddy/pick-library

Picks the right library for a frontend task from a curated, status-checked list, instead of hand-rolling it or installing an abandoned package. Use it when the user asks "what should I use for…", "is there a library for…", or names a package, and when you are about to build a toast, command menu, drawer, date picker, table, virtual list, drag and drop, chart, OTP input, animated number, theme switch, form validation…

Related Skills