Designing overlapped / cascaded UI with overlapui

Designing overlapped / cascaded UI with overlapui

●1 ●9 ●47
calendar_today ago • schedule4 min read
— Originally published at fscss.hashnode.dev

Build avatar stacks, notification piles, image strips, and radial menus with a token-driven CSS architecture—rest vs open states, no JavaScript for the interaction—using the overlapui FSCSS module.

Reference: overlapui.fscss


Stacked faces in a members column, cards that peek under each other, photos that share one edge, or a center control that fans into actions: that look is usually a pile of one-off rules. overlapui treats it as a small architecture—shared --ou-* tokens, four cascade patterns, hover and focus-within open states—and compiles to plain CSS.

This article is description- and code-based: how the layers fit, with copy-paste examples. The only project reference is the module itself.


What “cascaded UI” means here

A cascade has two densities:

  1. Rest — children share space (negative margin, peek, or stacked under a hub).

  2. Open — :hover or :focus-within restores gap, full card height, or a radial orbit.

Tokens describe density and chrome. Mixins encode the rest/open contract. Markup stays light lists, card roots, or a center-plus-satellites control.

Pattern Rest Open
Avatar horizontal pull-in sibling gap
Card vertical peek expanded margins
Image overlap + tilt align + gap
Circle satellites hidden under center polar fan-out

Setup

Use FSCSS 1.2.3+ (1.2.5+ recommended). Load the runtime with defer or async when prototyping.

<script src="https://cdn.jsdelivr.net/npm/fscss@1.2.5/runtime.min.js" defer></script>
@import((*) from overlapui)

@ou-root()
@avatar-overlap(.avatar-overlap)
@card-overlap(.card-overlap)
@img-overlap(.img-overlap)
@circle-overlap(.circle-overlap)
@ou-reduced-motion()

One-shot install (default class names):

@import((*) from overlapui)
@overlapui()

Selective import:

@import((ou-root, ou-avatar-colors avatar-overlap, circle-overlap, ou-reduced-motion) from overlapui)

@ou-root()
@avatar-overlap(.team)
@circle-overlap(.fab)
@ou-reduced-motion()

Layer 1 — tokens (--ou-*)

Theme the product by overriding tokens after @ou-root(), not by forking mixin internals.

@ou-root()

:root {
  --ou-ring: #0f172a;
  --ou-time: 0.35s;
  --ou-ease: cubic-bezier(0.3, 1.3, 0.5, 1);
  --ou-center-bg: #6366f1;

  @ou-avatar-size(44px, -14px)
  @ou-card-size(34px, 76px)
  @ou-img-size(96px, -52px)
  @ou-circle-size(48px, 80px)
}
Token area Variables (examples)
Motion / chrome --ou-time, --ou-ease, --ou-ring, --ou-ring-width
Avatar --ou-size, --ou-overlap, --ou-gap, --ou-c, --ou-letter
Card --ou-peek, --ou-card-h, --ou-card-gap, --ou-card-bg, --ou-card-title
Image --ou-img, --ou-img-overlap, --ou-img-gap, --ou-img-tilt
Circle --ou-item, --ou-r, --ou-center-bg, --n

Dense UI (tables, toolbars): smaller size, stronger overlap.

:root {
  @ou-avatar-size(28px, -10px)
}

Layer 2 — avatar cascade

Role: presence clusters—members, assignees, “also on this thread.”

<ul class="avatar-overlap" tabindex="0">
  <li data-alph="g">Gina Park</li>
  <li data-alph="h">
    <img src="https://i.pravatar.cc/80?img=12" alt="">
    Hank Lee
  </li>
  <li data-alph="5">5001</li>
</ul>
@ou-root()
@avatar-overlap(.avatar-overlap)

Behavior encoded by the module:

  • Rest: each face after the first uses margin-left: var(--ou-overlap).

  • Open: li + li uses var(--ou-gap).

  • Letter from data-alph via ::before; font-size: 0 on the face keeps names in the DOM for assistive tech.

  • Optional img covers the letter; letter remains fallback.

  • A–Z / 0–9 map to a palette through FSCSS arrays at compile time.

Custom selector:

@avatar-overlap(.members)
<ul class="members" tabindex="0">
  <li data-alph="a">Ada</li>
  <li data-alph="n">Nia</li>
</ul>

Layer 3 — card cascade

Role: notification piles, activity stacks, ticket previews without a drawer component.

<div class="card-overlap" tabindex="0">
  <div>
    <img src="https://picsum.photos/96?random=1" alt="">
    <h3>Invoice paid</h3>
    Acme Corp · $2,400
  </div>
  <div>
    <img src="https://picsum.photos/96?random=2" alt="">
    <h3>Seat added</h3>
    workspace update
  </div>
  <div>
    <img src="https://picsum.photos/96?random=3" alt="">
    <h3>Comment</h3>
    “Ship the cascade”
  </div>
</div>
@card-overlap(.card-overlap)

:root {
  --ou-peek: 34px;
  --ou-card-h: 76px;
  --ou-card-gap: 10px;
  --ou-card-bg: #1f2a48;
  --ou-card-title: #fff;
}

Rest uses a negative top margin derived from peek and card height so only a band of each card shows. Open restores var(--ou-card-gap) between siblings. Prefer animating margin/transform over toggling display.


Layer 4 — image cascade

Role: editorial strips, gallery teasers, “recent media.”

<div class="img-overlap" tabindex="0">
  <img src="https://picsum.photos/120?random=4" alt="">
  <img src="https://picsum.photos/120?random=5" alt="">
  <img src="https://picsum.photos/120?random=6" alt="">
</div>
@img-overlap(.img-overlap)

:root {
  @ou-img-size(96px, -48px)
  --ou-img-tilt: -5deg;
  --ou-img-tilt-even: 4deg;
}

Rest: overlap plus alternating tilt. Open: rotate(0) and gap between images.


Layer 5 — circle cascade

Role: settings hub, FAB-style actions, compact toolbars.

<div class="circle-overlap" style="--n:5" tabindex="0">
  <button type="button" aria-label="Settings menu">+</button>
  <a href="#profile" aria-label="Profile">P</a>
  <a href="#billing" aria-label="Billing">B</a>
  <a href="#team" aria-label="Team">T</a>
  <a href="#api" aria-label="API keys">K</a>
  <a href="#logout" aria-label="Sign out">×</a>
</div>
@circle-overlap(.circle-overlap)

:root {
  @ou-circle-size(48px, 80px)
  --ou-center-bg: #6366f1;
}

Rules of the pattern:

  • First child is the center control (background --ou-center-bg).

  • --n is the number of satellites (exclude the center).

  • Satellites sit under the center at rest (opacity: 0, scaled down), then fan with angle 360deg / var(--n) * var(--i).

  • Built-in --i / --c defaults apply per :nth-child; override --c when needed.

Topbar-friendly footprint:

.settings-wrap {
  position: relative;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.settings-orbit {
  --n: 5;
  --ou-item: 40px;
  --ou-r: 70px;
  margin: 0;
  width: var(--ou-item);
  height: var(--ou-item);
}
<div class="settings-wrap">
  <div class="settings-orbit circle-overlap" style="--n:5" tabindex="0">
    <button type="button" aria-label="Settings">+</button>
    <a href="#profile" aria-label="Profile">P</a>
    <a href="#billing" aria-label="Billing">B</a>
    <a href="#team" aria-label="Team">T</a>
    <a href="#api" aria-label="API">K</a>
    <a href="#out" aria-label="Sign out">×</a>
  </div>
</div>

Minimal full page (all four patterns)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>overlapui cascades</title>
  <script src="https://cdn.jsdelivr.net/npm/fscss@1.2.5/runtime.min.js" defer></script>
  <style>
    @import((*) from overlapui)

    @ou-root()
    @avatar-overlap(.avatar-overlap)
    @card-overlap(.card-overlap)
    @img-overlap(.img-overlap)
    @circle-overlap(.circle-overlap)
    @ou-reduced-motion()

    :root {
      --ou-ring: #0f172a;
      --ou-center-bg: #6366f1;
      @ou-avatar-size(44px, -14px)
      @ou-circle-size(48px, 80px)
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      gap: 48px;
      place-content: center;
      padding: 32px 16px;
      background: #0b1220;
      font-family: system-ui, sans-serif;
      color: #e2e8f0;
    }
  </style>
</head>
<body>
  <ul class="avatar-overlap" tabindex="0">
    <li data-alph="g">G</li>
    <li data-alph="h">H</li>
    <li data-alph="i">I</li>
    <li data-alph="j">J</li>
  </ul>

  <div class="card-overlap" tabindex="0">
    <div>
      <img src="https://picsum.photos/96?random=1" alt="">
      <h3>Title</h3>
      subtitle
    </div>
    <div>
      <img src="https://picsum.photos/96?random=2" alt="">
      <h3>Title</h3>
      subtitle
    </div>
  </div>

  <div class="img-overlap" tabindex="0">
    <img src="https://picsum.photos/96?random=4" alt="">
    <img src="https://picsum.photos/96?random=5" alt="">
    <img src="https://picsum.photos/96?random=6" alt="">
  </div>

  <div class="circle-overlap" style="--n:4" tabindex="0">
    <button type="button" aria-label="Menu">+</button>
    <a href="#a" aria-label="A">A</a>
    <a href="#b" aria-label="B">B</a>
    <a href="#c" aria-label="C">C</a>
    <a href="#d" aria-label="D">D</a>
  </div>
</body>
</html>

Responsive behavior (tokens, not new markup)

Scale density at breakpoints:

@media (max-width: 520px) {
  :root {
    @ou-avatar-size(36px, -11px)
    @ou-img-size(72px, -36px)
    @ou-circle-size(40px, 64px)
  }
}

Keep horizontal stacks from breaking the page:

body { overflow-x: hidden; }

.center-block {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
}

Accessibility (part of the contract)

  • Keep real labels or names in the DOM for avatars; faces may hide the decorative letter with font-size: 0.

  • Put aria-label on the circle center and on each satellite control.

  • Add tabindex="0" on stack roots so :focus-within matches hover.

  • Call @ou-reduced-motion() so transitions drop out when the user prefers reduced motion.


How the pieces fit (checklist)

  1. Choose a cascade: avatar, card, image, or circle.

  2. Set density with --ou-* / size helpers.

  3. Mark the stack root for keyboard open when needed.

  4. Theme only through tokens.

  5. Compile with FSCSS or use the runtime; the browser runs CSS.

Overlapped UI is a rest/open contract plus a token layer. overlapui is that contract as an FSCSS module.


Reference

2 Comments

1 vote
1
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

5 Web Dev Pitfalls That Are Silently Killing Your Projects (With Real Fixes)

Dharanidharan - Mar 3

Layout Grids in CSS: Designing with Millimeter Precision Before Writing Code

Joemetry - Sep 22

⚡ Creating Custom Functions in FSCSS (Yes, CSS Can Do This)

Figsh - Mar 28
chevron_left
1.5k Points • 57 Badges
20Posts
13Comments
12Connections
A full-stack web developer: Building smart solutions, designing meaningful experiences, and continuo... Show more

Related Jobs

View all jobs →

Commenters (This Week)

4 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!