Exclusivebeta
Exploded View — <mv-exploded>
A 3D exploded view of any live interface: every box becomes a plane lifted by its visual depth (invisible wrappers don’t add a floor), the whole assembly tilts into an isometric pose, orbits by drag or keyboard, isolates a layer on hover (tag, classes, size), labels layers with leader lines, then reassembles on per-floor springs and restores the original styles byte for byte.
| Category | 3D |
|---|---|
| Type | Web Component (<mv-exploded>) |
| Status | beta |
| Keywords | exclusive, 3d, exploded-view, anatomy, inspector, preserve-3d, layers, landing, devtools, spring |
When to use
- A landing page should reveal the anatomy of a live interface as layered planes in 3D
- A design system or devtools demo needs to show how a component is built, with labeled layers
- A scroll-driven section should explode and reassemble a UI as the visitor passes through
Avoid when
- A flat device frame is enough to present the interface → use Device Mockup instead
- The wrapped interface must stay usable, since clicks are intercepted while it is exploded
- The subtree has hundreds of elements; layer caps protect performance but the view gets cluttered
Install
node scripts/add.mjs exploded --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["exploded"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/exploded/exploded.js, components/exploded/exploded.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div class="ex-demo">
<style>
.ex-demo { display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); align-items: center; gap: clamp(16px, 4vw, 56px); min-height: 620px; padding: 32px clamp(20px, 4vw, 56px); box-sizing: border-box; }
@media (max-width: 820px) { .ex-demo { grid-template-columns: 1fr; } }
.ex-eyebrow { margin: 0 0 10px; font: 600 12px/1 var(--mv-font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--mv-accent-fg); }
.ex-copy h2 { margin: 0; font: 750 clamp(1.8rem, 3vw, 2.4rem)/1.05 var(--mv-font-sans); letter-spacing: -.035em; }
.ex-copy > p:not(.ex-eyebrow) { margin: 14px 0 0; color: var(--mv-fg-muted); font-size: .95rem; line-height: 1.55; }
.ex-facts { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 10px; font-size: .88rem; }
.ex-facts li { display: flex; gap: 10px; align-items: baseline; }
.ex-facts b { font: 700 1rem/1 var(--mv-font-sans); min-width: 2.2em; color: var(--mv-fg); }
.ex-facts span { color: var(--mv-fg-muted); }
.ex-keys { margin-top: 22px; font-size: .78rem; color: var(--mv-fg-subtle); }
.ex-keys kbd { font: 600 11px/1 var(--mv-font-mono); padding: 3px 6px; border-radius: 5px; border: 1px solid var(--mv-border-strong); border-bottom-width: 2px; background: var(--mv-surface); color: var(--mv-fg); }
.ex-view { min-height: 540px; display: grid; place-items: center; }
.ex-card { width: min(100%, 340px); border-radius: 22px; background: var(--mv-surface-raised); border: 1px solid var(--mv-border); box-shadow: var(--mv-shadow-xl); overflow: hidden; }
.ex-media { position: relative; height: 176px; background: linear-gradient(180deg, #8ec5ff 0%, #cfe6ff 55%, #f6e7d4 100%); }
.ex-media svg { position: absolute; inset: auto 0 0; width: 100%; height: 120px; }
.ex-badge { position: absolute; top: 14px; left: 14px; padding: 6px 10px; border-radius: 999px; background: oklch(0.99 0 0 / .92); color: oklch(0.25 0.03 270); font: 650 11.5px/1 var(--mv-font-sans); box-shadow: 0 4px 12px oklch(0.2 0.05 260 / .18); }
.ex-fav { position: absolute; top: 10px; right: 10px; width: 36px; height: 36px; border-radius: 50%; border: 0; display: grid; place-items: center; background: oklch(0.99 0 0 / .92); color: oklch(0.62 0.22 20); box-shadow: 0 4px 12px oklch(0.2 0.05 260 / .18); cursor: pointer; }
.ex-fav svg { position: static; width: 18px; height: 18px; }
.ex-body { padding: 16px 18px 18px; display: grid; gap: 10px; }
.ex-host { position: relative; display: flex; align-items: center; gap: 10px; margin-top: -38px; }
.ex-avatar { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; background: linear-gradient(135deg, oklch(0.7 0.15 40), oklch(0.6 0.18 350)); color: white; font: 700 14px/1 var(--mv-font-sans); border: 3px solid var(--mv-surface-raised); box-shadow: var(--mv-shadow-md); }
.ex-host small { margin-top: 30px; font-size: 12px; color: var(--mv-fg-muted); }
.ex-body h3 { margin: 2px 0 0; font: 700 1.15rem/1.2 var(--mv-font-sans); letter-spacing: -.02em; }
.ex-meta { margin: -4px 0 0; font-size: .82rem; color: var(--mv-fg-muted); }
.ex-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.ex-tags span { padding: 4px 9px; border-radius: 999px; background: var(--mv-bg-muted); border: 1px solid var(--mv-border); font-size: 11.5px; color: var(--mv-fg-muted); }
.ex-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 4px; padding-top: 12px; border-top: 1px solid var(--mv-border); }
.ex-price { font-size: .82rem; color: var(--mv-fg-muted); }
.ex-price b { font: 750 1.25rem/1 var(--mv-font-sans); color: var(--mv-fg); letter-spacing: -.02em; }
</style>
<div class="ex-copy">
<p class="ex-eyebrow">Design system · Refuge</p>
<h2>How it’s built</h2>
<p>This booking card isn’t a mockup: it’s the real DOM, exploded by visual depth. Orbit it, hover a layer to isolate it, click to pin it.</p>
<ul class="ex-facts">
<li><b>0</b><span>lines of code added to the card</span></li>
<li><b>1:1</b><span>original styles restored on reassembly</span></li>
<li><b>3D</b><span>native preserve-3d, no DOM copies</span></li>
</ul>
<p class="ex-keys"><kbd>Enter</kbd> explode · <kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd><kbd>↓</kbd> orbit · <kbd>+</kbd><kbd>−</kbd> spacing · <kbd>Esc</kbd></p>
</div>
<mv-exploded class="ex-view" controls exploded aria-label="Booking card in exploded view">
<article class="ex-card" data-mv-layer="Card">
<div class="ex-media" data-mv-layer="Media">
<svg viewBox="0 0 340 120" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 120 L0 70 L60 22 L96 52 L142 8 L204 64 L240 40 L300 86 L340 60 L340 120 Z" fill="#6d86b8"/>
<path d="M142 8 L128 22 L138 20 L146 30 L154 18 L164 26 Z" fill="#fff"/>
<path d="M0 120 L0 92 L70 60 L130 96 L190 70 L260 100 L340 80 L340 120 Z" fill="#3f5f8f"/>
<path d="M0 120 L0 108 L90 92 L180 110 L270 96 L340 106 L340 120 Z" fill="#2b4169"/>
</svg>
<span class="ex-badge" data-mv-layer="Badge">Guest favorite</span>
<button class="ex-fav" type="button" aria-label="Add to favorites" data-mv-layer="Favorite">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 21s-7.5-4.6-9.6-9.2C.9 8.4 3 4.5 6.8 4.5c2.1 0 3.6 1.1 5.2 3 1.6-1.9 3.1-3 5.2-3 3.8 0 5.9 3.9 4.4 7.3C19.5 16.4 12 21 12 21z"/></svg>
</button>
</div>
<div class="ex-body">
<div class="ex-host"><span class="ex-avatar" data-mv-layer="Host">MC</span><small>Maya, host since 2019</small></div>
<h3>Arolla Chalet</h3>
<p class="ex-meta">Zermatt · 6 guests · 3 bedrooms</p>
<div class="ex-tags" data-mv-layer="Amenities"><span>Sauna</span><span>Matterhorn view</span><span>Ski-in/ski-out</span></div>
<div class="ex-foot">
<div class="ex-price"><b>$460</b> / night</div>
<a class="mv-button" href="#" data-mv-layer="Primary action">Book</a>
</div>
</div>
</article>
</mv-exploded>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
exploded | boolean | Exploded state (reflected; setting it on load animates the opening). | |
trigger | toggle | hover | scroll | manual | toggle | toggle: keyboard, toolbar or API; hover: explodes on hover/focus; scroll: progress tied to scrolling (20 → 50% of the way through the viewport). |
gap | number (px) | 62 | Z spacing between two visible floors. |
depth | visual | dom | visual | visual: only elements that paint (background, border, shadow, text, media) add a floor; dom: one floor per nesting level. |
max-depth | number | 10 | Maximum number of floors (deeper elements stay on the last one). |
max-layers | number | 600 | Performance safeguard. |
tilt / spin | number (deg) | 54 / -32 | Exploded pose: tilt (rotateX) and spin (rotateZ). |
scale | number | 0.84 | Scales the assembly down once exploded (so it stays in frame). |
labels | auto | authored | none | auto | Labels: those from data-mv-layer, otherwise (auto) the largest plane on each floor with its tag.class. |
controls | boolean | Built-in toolbar (Explode/Assemble, Reset view). | |
data-mv-layer (descendant) | string | Layer name shown in a callout with a leader line. | |
data-mv-depth (descendant) | number | Forces how many floors this element adds (0 = stays on its parent’s plane). |
Properties
| Name | Type | Description |
|---|---|---|
stats | { layers, levels } | Number of planes and floors in the current view. |
Methods
| Name | Description |
|---|---|
explode() / assemble() / toggle(force?) | Programmatic control. |
resetView() | Returns to the default pose after orbiting. |
Events
| Name | Description |
|---|---|
mv-explode / mv-assemble | State change. detail: { layers, levels }. |
mv-layer-hover | A plane is hovered. detail: { element, level }. |
CSS classes
| Name | Description |
|---|---|
mv-exploded-hud / -leaders / -callout / -tip | Annotation layer (aria-hidden): SVG leader lines, callouts, tooltip for the hovered plane. |
mv-exploded-toolbar / -btn / -hint | Toolbar (controls attribute). |
[data-mv-exploded-layer|leaf|ghost|clip|hover|pinned] | Temporary markers set on planes during the exploded view (removed on reassembly). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-exploded-color | var(--mv-accent) | Wireframe, hover and leader lines. |
--mv-exploded-perspective | 1800px | Scene perspective. |
--mv-exploded-progress | 0 → 1 while exploding (read-only, usable in CSS). |
Accessibility
The host is focusable (role=group, aria-roledescription “exploded view”, aria-keyshortcuts): Enter/Space explodes or assembles, arrows orbit (Shift = faster), +/− adjust the spacing, Home resets, Escape unpins then assembles. The state is announced in a live region. While inspecting, clicks inside the interface are intercepted (you inspect, you don’t activate); the content stays readable by screen readers. Annotations are aria-hidden, the toolbar is accessible. Reduced motion: no springs or per-floor offsets, the pose applies instantly.