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 (data-transition), while duration and easing come from VB motion tokens — so themes automatically control the personality of every swap.

<content-swap data-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
data-transition string flip Transition effect: flip, flip-vertical, fade, slide-left, slide-up, scale.
data-swapped boolean false Reflects the current swap state. Can be set to start swapped.
data-card boolean Applies layout-card visual shell (background, radius, shadow).
data-variant string Card variant when using data-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 swap

Faded in

Slide Left

Click to swap

Slid in

Scale

Click to swap

Scaled in

<content-swap data-transition="fade"> <div data-face="front">Front</div> <div data-face="back">Back</div> </content-swap> <content-swap data-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 data-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 data-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 data-transition="flip" data-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 data-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 data-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.

Native article

This is a regular <article> with data-swap.

Still an article

Same swap behavior, no custom element wrapper needed.

<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 face el.flip(); // Swap to front face el.unflip(); // Toggle current state el.toggle(); // Check state console.log(el.swapped); // true or false

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-swap children): The element gets role="button", tabindex="0", and aria-label="Toggle content".
  • Explicit triggers (data-swap children): 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 Elements