content-swap
Two-face content toggle with flip, fade, slide, and scale transitions. Theme-driven motion via design tokens.
Overview
The <content-swap> component toggles between two content faces with configurable transition effects. It manages inert on the hidden face so screen readers only announce visible content.
The transition type is a styling concern (transition), while duration and easing come from VB motion tokens — so themes automatically control the personality of every swap.
<content-swap transition="flip"> <div data-face="front"> <p>Front content visible by default.</p> </div> <div data-face="back"> <p>Back content revealed on swap.</p> </div></content-swap>
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
transition |
string | flip |
Transition effect: flip, flip-vertical, fade, slide-left, slide-up, scale. |
swapped |
boolean | false |
Reflects the current swap state. Can be set to start swapped. |
card |
boolean | — | Applies layout-card visual shell (background, radius, shadow). |
data-variant |
string | — | Card variant when using card: elevated, outlined, ghost. |
Child Attributes
| Attribute | Where | Description |
|---|---|---|
data-face="front" |
Direct child | Marks the front face. |
data-face="back" |
Direct child | Marks the back face. |
data-swap |
Any descendant | Marks an element as a swap trigger button. |
Transition Effects
Each transition reads from VB motion tokens, so themes automatically control personality.
| Value | Animation | Kawaii | Brutalist |
|---|---|---|---|
flip |
3D rotateY | Bouncy easing, 300ms | Instant cut |
flip-vertical |
3D rotateX | Bouncy easing | Instant cut |
fade |
Crossfade opacity | 300ms fade | Hard opacity toggle |
slide-left |
Horizontal slide | Slide with bounce | Hard slide, linear |
slide-up |
Vertical slide | Slide with bounce | Hard slide, linear |
scale |
Scale + opacity | Pop with bounce | Linear scale |
Fade
Click to swapFaded in
Slide Left
Click to swapSlid in
Scale
Click to swapScaled in
<content-swap transition="fade"> <div data-face="front">Front</div> <div data-face="back">Back</div></content-swap>
<content-swap transition="slide-left"> <div data-face="front">Front</div> <div data-face="back">Back</div></content-swap>
Trigger Buttons
By default, clicking anywhere on the element triggers the swap. Add data-swap to specific buttons for explicit trigger controls. When triggers exist, the container is no longer clickable.
Question: What does inert do?
Answer: It makes an element and its descendants non-interactive and hidden from assistive technology.
<content-swap transition="fade"> <div data-face="front"> <p>What does inert do?</p> <button data-swap>Show answer</button> </div> <div data-face="back"> <p>It makes an element non-interactive.</p> <button data-swap>Back to question</button> </div></content-swap>
Card Mode
Add card to apply layout-card's visual shell directly on the swap element. Supports data-variant for elevated, outlined, and ghost styles.
Product
Flip to see details.
Details
Price, specs, and more.
Outlined
Card mode with outlined variant.
Back
Still outlined.
<content-swap transition="flip" card> <div data-face="front"> <h3>Product</h3> <p>Flip to see details.</p> </div> <div data-face="back"> <h3>Details</h3> <p>Price, specs, and more.</p> </div></content-swap>
Page-level auto-card
Add data-swap-autocard to a container or <body> to automatically apply card chrome to all content-swap elements that don't already wrap <layout-card> children.
<body data-swap-autocard> <content-swap transition="slide-left"> <div data-face="front">Auto-styled as card</div> <div data-face="back">Auto-styled as card</div> </content-swap></body>
Composition
Wrap <layout-card> elements as faces for full card structure (header, section, footer) on each side.
Front Card
Full card structure with header, section, and footer.
Back Card
Each face is an independent layout-card.
<content-swap transition="flip"> <layout-card data-face="front"> <header><h3>Front</h3></header> <section><p>Full card structure per face.</p></section> <footer><button data-swap>See back</button></footer> </layout-card> <layout-card data-face="back"> <header><h3>Back</h3></header> <section><p>Each face is a layout-card.</p></section> <footer><button data-swap>See front</button></footer> </layout-card></content-swap>
Attribute Form
For authors who want swap behavior on an existing element, add data-swap as an attribute on any element with data-face children. The behavior auto-initializes via JavaScript.
<article data-swap data-transition="fade"> <div data-face="front">Front content</div> <div data-face="back">Back content</div></article>
Keyboard Navigation
| Key | Action |
|---|---|
| Enter / Space | Toggle swap (when element or trigger is focused) |
| Tab | Move focus to next interactive element |
Events
| Event | Detail | Description |
|---|---|---|
content-swap:swap |
{ swapped: boolean } |
Fired after the content swaps. |
const el = document.querySelector('content-swap'); el.addEventListener('content-swap:swap', (event) => { console.log('Swapped:', event.detail.swapped);});
JavaScript API
| Method / Property | Description |
|---|---|
flip() |
Swap to show the back face. |
unflip() |
Swap to show the front face. |
toggle() |
Toggle between front and back. |
swapped |
Boolean property reflecting the current state. |
flipped |
Alias for swapped (backward compatibility). |
const el = document.querySelector('content-swap'); // Swap to back faceel.flip(); // Swap to front faceel.unflip(); // Toggle current stateel.toggle(); // Check stateconsole.log(el.swapped); // true or false/code-block
Theme Integration
Content-swap reads from VB motion tokens. Themes override these tokens, so personality comes for free:
- Default:
--motion-enter-duration: 300ms,--ease-default: var(--ease-3) - Kawaii: Bouncy easing (
cubic-bezier(0.34, 1.56, 0.64, 1)), 300ms - Brutalist: Linear easing, 0ms (instant cut)
No extra theme CSS needed — the token bridge handles it.
Accessibility
Inert Toggling
The hidden face is always marked inert. Screen readers only announce the currently visible face, and interactive elements on the hidden face cannot receive focus.
Trigger Modes
- Whole-element trigger (no
data-swapchildren): The element getsrole="button",tabindex="0", andaria-label="Toggle content". - Explicit triggers (
data-swapchildren): The trigger buttons are the keyboard targets. No role is added to the container.
Focus Management
When swapping, focus moves to the first interactive element on the newly visible face (buttons, links, inputs, or elements with autofocus).
Reduced Motion
When prefers-reduced-motion: reduce is active, the swap animation duration is set to 0s. The state still toggles instantly without animation.
Progressive Enhancement
Without JavaScript, both faces display stacked in natural document flow. The :not(:defined) selector ensures transforms only apply once the component is registered.
Related
<layout-card>— Card visual shell (compose with content-swap)<accordion-wc>— Collapsible content panels<tab-set>— Tab panels for content switching