Exploded UI

A product screenshot that comes apart into its layers as you scroll, explains each one, then clicks back together. The layers are real HTML, so text stays sharp and follows your theme.

  • Scroll
  • 3D
  • Showcase

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude

Inside Northwind

Everything your revenue team needs, in one window

Scroll to take it apart.

app.northwind.io/overview
Overview
Search⌘K
RevenueLast 12 weeks
Top accounts
  • Acme Corp$48k
  • Globex$36k
  • Initech$22k
  • Umbrella$17k
Northwind
OverviewPipelineAccountsReportsWorkspacesAcme CorpGlobexInitechUmbrella
RevenueLive
$48.2k
+12.4% this week
Active usersLive
2,931
+8.1% this week
ConversionLive
3.8%
+0.6% this week
Type a command…
SuggestionsCreate invoiceIInvite teammateTExport reportESwitch workspaceW
Maya

Built for speed

Every screen loads in under 100ms, even with a million rows.

Keyboard first

Anything you can click, you can do from the command menu.

Live by default

Numbers, comments and cursors update the moment they change.

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/exploded-ui.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/exploded-ui

Examples

Finished sections built with Exploded UI. Each one installs whole, with the component included.

Architecture

The layers don't have to be UI. Four plain planes, from your app down to the database, that stay open at the end. Each carries its name on its front edge, the strip that stays in view.

How it works

One request, four layers, 40 milliseconds

usr_8f2amaya@northwind.ioTeam2m ago
usr_31cdjonas@globex.comPro14m ago
usr_77beada@initech.devTeam1h ago
usr_02f9lin@umbrella.coFree3h ago
Databasepostgres 16 · 3 replicas
Edge network300 cities · p50 12ms
import { createClient } from "@acme/sdk";

const acme = createClient({ region: "auto" });
const user = await acme.users.get(id);
SDK@acme/sdk · 9kb
NorthwindProjectsUsageSettings
ProjectsNew project
StorefrontReady · 2m ago
AdminBuilding · just now
DocsReady · 1h ago
MarketingReady · 3h ago
Mobile APIReady · 1d ago
BillingQueued · 1d ago
Your appnext.js · react

Read the architecture guide for the details.

Design System

A page lifts off the components it's built from, which lift off the tokens under them. Numbered labels, with the active layer's description under the stack.

Fieldwork Design System

Every page starts as three decisions

color
Ink
Clay
Moss
typeAadisplay40 / 44title24 / 30body16 / 24space
PrimarySecondaryAccent
Shipped
Emailyou@fieldwork.co
Fieldwork
ProductPricingSign up
Shipped: v4

Field notes for teams that build

Start freeBook a demo
Join the betayou@fieldwork.coWeekly digestRequest access

Color, type and spacing, decided once.

Buttons, fields and toggles built from the tokens.

Composed from components. Nothing one-off.

120 components, 2 themes, 0 hex codes in product code.

Props

childrenRequired

TypeReact.ReactNode

The layers, as ExplodedUI.Layers. The first child is the bottom of the stack and each one after sits on top. In a Server Component, import and use ExplodedUILayer instead: the dot doesn't reach across to client components.

walkthrough

TypebooleanDefaultfalse

Once the stack is open, step through the labeled layers from the bottom up: the active one lifts a little further while the others fade back, and its label opens to show its description. Layers above it fade out to their outlines, so it's never covered. Click a label to scroll the walkthrough to it.

end

Type"assembled" | "exploded"Default"assembled"

assembled drops the layers back bottom first and lands the flat screenshot exactly where it started. exploded gives the walkthrough the rest of the scroll and leaves the stack open as the section scrolls away, for a "how it's built" section.

labels

Type"side" | "numbered" | falseDefault"side"

side puts the labels in a column beside the stack with dotted leaders to their layers. numbered pins a number on each layer's front edge and lists the names under the stack. Side labels become numbered on stages narrower than 640px.

tilt

Type{ back?: number; turn?: number }Default{ back: 50, turn: -30 }

The viewing angle once tilted, in degrees: the plane turns on the page by turn, then leans away by back. Narrow stages get 80% of it.

spread

TypenumberDefault0.12

The gap between neighboring layers once open, as a share of the plane's width, so it scales with the screenshot. Fewer layers can take more.

scrollLength

TypenumberDefault3, or 4 with walkthrough

How long the section stays stuck, in stage heights. The tilt, the explode and the return each take a fixed share of it; the walkthrough splits its share evenly between the layers.

interactive

TypebooleanDefaulttrue

While the stack is open, hovering a layer or its label (or focusing a label) lifts it like the walkthrough does, and moving the mouse turns the stack up to 4°. Without a walkthrough, clicking a label keeps its layer highlighted. Pointer tilt is mouse only.

onLayerChange

Type(index: number | null) => void

Called with a layer's index, counted from the bottom, as the walkthrough reaches it, and with null as the walkthrough leaves the layers.

containerRef

TypeReact.RefObject<HTMLElement | null>

Scroll container to track. Defaults to the window. The container must be positioned (e.g. relative).

stageClassName

TypestringDefault"h-dvh"

Classes for the stage that sticks while the section scrolls. Set its height and background here. The labels are drawn on it, outside the plane.

className

TypestringDefault"aspect-[16/10] w-full max-w-5xl"

Classes for the plane: the screenshot's box. Size it here. It's a container, so layer content can size itself in cqw units and scale like an image. Other props pass through to the outer section.

Layer.label

TypeReact.ReactNode

The layer's name, shown once it lifts off. Layers without one aren't labeled or visited by the walkthrough.

Layer.description

TypeReact.ReactNode

One line about the layer, shown under its label while it's active.

Layer.className

TypestringDefault"inset-0"

Places and sizes the layer on the plane, like left-0 top-0 h-full w-[22%]. Without any position or size, it fills the plane. Each layer is a sheet with slightly rounded corners that clips its content; its edge and the light along its top fade in as it lifts off.

Layer.bare

TypebooleanDefaultfalse

Drops the sheet: no clipping, no edge, and no shadow cast or received. For pieces that float on their own, like a cursor.