Exclusivebeta

Origami — <mv-origami>

Panels that fold like paper. Fold variant: an accordion on native <details> whose content is sliced into strips that unfold zigzag-style in 3D, with light and shadow on every fold, glints on the edges, paper grain, a spring bounce and creases that stay visible once open. Letter variant: a sealed envelope that opens, its letter slides out, comes to the front and unfolds in three panels, inline or in a modal dialog, for an announcement or an invitation.

CategoryData display
TypeWeb Component (<mv-origami>)
Statusbeta
Keywordsexclusive, accordion, disclosure, details, faq, fold, paper, 3d, letter, envelope, announcement, modal, invitation

When to use

Avoid when

Install

node scripts/add.mjs origami --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["origami"], 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/origami/origami.js, components/origami/origami.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="og-demo" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr));gap:2rem 2.5rem;width:min(100%,62rem);margin-inline:auto;align-items:start">
  <style>
    #og-demo h3 { margin:0 0 .75rem; font-size:.75rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--mv-fg-muted) }
    #og-demo .mv-origami-sheet p { margin:0 0 .6rem }
    #og-demo .mv-origami-sheet ul { margin:.25rem 0 .6rem; padding-left:1.1rem }
    #og-demo .og-letter h4 { margin:0 0 .15rem; font-size:1.375rem; letter-spacing:-.01em }
    #og-demo .og-letter .og-date { margin:0 0 1.1rem; color:var(--mv-fg-muted); font-size:.75rem; letter-spacing:.04em; text-transform:uppercase }
    #og-demo .og-letter p { margin:0 0 .75rem }
    #og-demo .og-letter .og-where { display:grid; grid-template-columns:auto 1fr; gap:.2rem .9rem; margin:1rem 0; padding:.75rem .9rem; border-radius:var(--mv-radius-md); background:color-mix(in oklab, var(--mv-accent) 7%, transparent); font-size:.8125rem }
    #og-demo .og-letter .og-where dt { color:var(--mv-fg-muted) }
    #og-demo .og-letter .og-where dd { margin:0; font-weight:500 }
    #og-demo .og-sign { margin:1rem 0 1.25rem; font:italic 500 1.125rem/1.2 "Segoe Script","Bradley Hand",cursive }
    #og-demo .og-actions { display:flex; gap:.5rem; flex-wrap:wrap }
    #og-demo .og-hint { margin:.9rem 0 0; text-align:center; color:var(--mv-fg-muted); font-size:.75rem }
  </style>

  <section aria-labelledby="og-faq-t">
    <h3 id="og-faq-t">Frequently asked questions</h3>
    <mv-origami id="og-faq" type="single">
      <details>
        <summary>How long does shipping take?</summary>
        <p>Orders placed before 2 p.m. ship the same day from our Brooklyn studio.</p>
        <ul>
          <li>Standard: 3–5 business days, free over $60</li>
          <li>Express: next-day delivery by 1 p.m.</li>
          <li>Pickup point: 48 hours, at 8,000+ locations nationwide</li>
        </ul>
        <p>You’ll get a tracking link by email as soon as your order ships.</p>
      </details>
      <details>
        <summary>Can I return an item?</summary>
        <p>Yes, you have 30 days after delivery to change your mind. Returns are free at any pickup point: print the label from your account, drop off the package, done.</p>
        <p>Refunds are issued to the original payment method within 5 business days of the package reaching the studio.</p>
      </details>
      <details>
        <summary>Where are your lamps made?</summary>
        <p>Every shade is shaped by hand in our Brooklyn studio. The ceramic bases come from a family-run pottery in Vermont, and the fabric cords are woven in North Carolina.</p>
      </details>
      <details>
        <summary>Do you offer a warranty?</summary>
        <p>Every lamp comes with a 5-year warranty on parts and labor. If something breaks, we repair it instead of replacing it — that’s craftsmanship too.</p>
      </details>
    </mv-origami>
  </section>

  <section aria-labelledby="og-letter-t">
    <h3 id="og-letter-t">Announcement</h3>
    <mv-origami variant="letter" id="og-letter" label="News from the studio">
      <div class="mv-origami-address"><small>Nomad Studio · Brooklyn</small>For you, Priya</div>
      <article class="mv-origami-letter og-letter">
        <h4>We’re moving!</h4>
        <p class="og-date">Brooklyn, Sep 23</p>
        <p>Dear Priya,</p>
        <p>After six wonderful years on Bergen Street, the studio is growing. We’ll finally have room to welcome you while we work, and to run lampshade-making classes on Saturdays.</p>
        <dl class="og-where">
          <dt>Address</dt><dd>14 Water Street, Brooklyn</dd>
          <dt>Grand opening</dt><dd>Saturday, Oct 18, from 11 a.m.</dd>
        </dl>
        <p>We’d love to raise a glass with you that day.</p>
        <p class="og-sign">Lena &amp; the whole team</p>
        <div class="og-actions">
          <button class="mv-button" type="button">I’ll be there</button>
          <button class="mv-button" data-variant="ghost" type="button" data-origami-close>Fold the letter</button>
        </div>
      </article>
    </mv-origami>
    <p class="og-hint">Click the envelope · <button class="mv-button" data-variant="link" data-size="sm" type="button" data-origami-open="og-modal">Open in a dialog</button></p>

    <mv-origami variant="letter" id="og-modal" modal label="Grand opening invitation">
      <div class="mv-origami-address"><small>Nomad Studio · Brooklyn</small>For you, Priya</div>
      <article class="mv-origami-letter og-letter">
        <h4>You’re invited</h4>
        <p class="og-date">Saturday, Oct 18 · 11 a.m.</p>
        <p>Come see the new studio at 14 Water Street: live shade-making demos, coffee from the roaster next door, and 15% off the whole collection for the occasion.</p>
        <p class="og-sign">See you soon, Lena</p>
        <div class="og-actions">
          <button class="mv-button" type="button" data-origami-close>Thanks, see you Saturday!</button>
        </div>
      </article>
    </mv-origami>
  </section>

  <script type="module">
    await customElements.whenDefined("mv-origami");
    const faq = document.getElementById("og-faq");
    // Show the fold once, a moment after the page appears.
    setTimeout(() => faq.toggle(0, true), 500);
  </script>
</div>

API

Attributes

NameTypeDefaultDescription
variantfold | letterfoldFolded accordion, or a letter in its envelope.
foldsnumberauto (≈ 1 fold / 52 px, 3 to 10)fold: number of paper strips.
typesingle | multiplemultiplefold: single folds the other panels when one opens (animated, unlike the native name attribute).
openbooleanletter: letter unfolded (on load: opened without animation).
modalbooleanletter: the envelope opens in a modal <dialog> (Escape or a backdrop click folds it back).
labelstringA letter for youletter: name of the envelope (button, back of the letter, dialog).
data-origami-closebooleanOn a button inside the letter: folds it back into the envelope.
data-origami-openidOn a button anywhere: opens the letter <mv-origami id="…">.

Methods

NameDescription
toggle(index | details, force?)fold: opens / folds a panel; letter: toggles the letter.
show() / hide()letter: unfolds / folds.

Events

NameDescription
mv-toggleStart of an opening or closing. detail: { open, item? }.

Content structure

NameDescription
details > summary + contentfold: everything after the <summary> becomes the sheet (.mv-origami-sheet, created if missing).
.mv-origami-letterletter: the letter content (real, accessible content).
.mv-origami-addressletter: handwritten text on the back of the envelope (<small> for the sender).

CSS classes

NameDescription
mv-origami-item / -panel / -sheetfold: <details> element, animated container and paper sheet.
mv-origami-stage / -strip / -face / -shadefold: cloned strips (aria-hidden) while folding.
mv-origami-desk / -envelope / -env-*letter: scene, open button and envelope parts (back, flap, pocket, seal).
mv-origami-folded / -leaf-*letter: letter folded in three during the animation.

CSS variables

NameDefaultDescription
--mv-origami-paperwarm off-white / deep grayPaper color.
--mv-origami-inkvar(--mv-fg)Text color on the paper.
--mv-origami-creaseStroke of the creases that stay visible.
--mv-origami-texturePaper grain (image, none to remove it).
--mv-origami-envelope / --mv-origami-sealEnvelope kraft paper, seal wax.
--mv-origami-letter-width34remLetter width.
--mv-origami-perspective1800px3D depth of the fold.
--mv-origami-scriptHandwriting font for the address.

Accessibility

fold: native <details>/<summary> semantics (Enter, Space, find-in-page opens the right panel); the animated strips are aria-hidden, inert clones with no duplicate ids, and the real content takes over as soon as the paper lies flat. letter: the envelope is a real button (aria-expanded, aria-controls); once unfolded, the letter (a region named by its heading) receives focus; “Fold” returns focus to the envelope; in modal mode, a native <dialog> with focus trap, Escape and focus return. Reduced motion: instant opening, the creases stay drawn on the paper as a static cue. Loops only run during an animation.