Beta

Card Stack <mv-card-stack>

Wischbarer Kartenstapel: Die Karten liegen jeweils leicht schräg und richten sich auf, während die oberste weggezogen wird; die oberste Karte dreht sich um den Punkt, an dem du sie gegriffen hast, ein Urteils-Badge und eine Randtönung wachsen mit dem Zug, während sich der passende Button füllt, Würfe behalten den Schwung der Geste, und next() schiebt die Karte unter den Stapel; Buttons, Pfeiltasten, Rückgängig, Schleife oder Ende und Autoplay.

Kategorie3D
TypWeb Component (<mv-card-stack>)
StatusBeta
Keywordscard-stack, swipe, deck, pile, fling, drag, testimonials, stack

When to use

  • Users must accept or skip items one at a time, as in matching, curation or onboarding choices
  • Testimonials or highlights should cycle as a tactile, slightly tilted pile with autoplay
  • Swipe decisions need undo, arrow-key control and announced verdicts

Avoid when

  • Items should be browsed or compared freely rather than decided on one by one → use Carousel instead
  • Cards should stack as the page scrolls, with no swipe gesture → use Scroll Stack instead

Installation

node scripts/add.mjs card-stack --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["card-stack"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/pointer.js, components/card-stack/card-stack.js, components/card-stack/card-stack.css.

Verwendung

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:3rem;align-items:start;width:100%;max-width:52rem">
  <div style="display:grid;gap:1rem;justify-items:center">
    <mv-card-stack id="cs-demo" label="Weekend ideas" accept-label="I’m in" reject-label="Skip">
    <article>
      <div aria-hidden="true" style="aspect-ratio:4/3;background:radial-gradient(circle at 30% 24%,#ecfeff 0 8%,transparent 8.5%),linear-gradient(150deg,transparent 57%,#0f766e 57.3%),linear-gradient(200deg,transparent 67%,#134e4a 67.3%),linear-gradient(180deg,#0ea5e9,#67e8f9 62%,#ecfeff)"></div>
      <div style="padding:1rem 1.1rem 1.2rem;display:grid;gap:.35rem">
        <h4 style="margin:0;font:700 1.15rem/1.2 var(--mv-font-sans);letter-spacing:-.01em">Banff</h4>
        <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">Canada · 2 days · from $180</p>
        <p style="margin:.35rem 0 0;display:flex;flex-wrap:wrap;gap:.35rem"><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Kayaking</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Hiking</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Camping</span></p>
      </div>
    </article>
    <article>
      <div aria-hidden="true" style="aspect-ratio:4/3;background:radial-gradient(circle at 41% 30%,#f8fafc 0 8%,transparent 8.5%),linear-gradient(157deg,transparent 57%,#e2e8f0 57.3%),linear-gradient(194deg,transparent 67%,#475569 67.3%),linear-gradient(180deg,#64748b,#bae6fd 62%,#f8fafc)"></div>
      <div style="padding:1rem 1.1rem 1.2rem;display:grid;gap:.35rem">
        <h4 style="margin:0;font:700 1.15rem/1.2 var(--mv-font-sans);letter-spacing:-.01em">Cliffs of Moher</h4>
        <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">Ireland · 1 day · from $65</p>
        <p style="margin:.35rem 0 0;display:flex;flex-wrap:wrap;gap:.35rem"><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Cliffs</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Cycling</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Seafood</span></p>
      </div>
    </article>
    <article>
      <div aria-hidden="true" style="aspect-ratio:4/3;background:radial-gradient(circle at 52% 36%,#fdba74 0 8%,transparent 8.5%),linear-gradient(164deg,transparent 57%,#4c1d95 57.3%),linear-gradient(188deg,transparent 67%,#1e1b4b 67.3%),linear-gradient(180deg,#6d28d9,#f472b6 62%,#fdba74)"></div>
      <div style="padding:1rem 1.1rem 1.2rem;display:grid;gap:.35rem">
        <h4 style="margin:0;font:700 1.15rem/1.2 var(--mv-font-sans);letter-spacing:-.01em">Lake Como</h4>
        <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">Italy · 3 days · from $240</p>
        <p style="margin:.35rem 0 0;display:flex;flex-wrap:wrap;gap:.35rem"><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Lake</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Paddleboarding</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Gelato</span></p>
      </div>
    </article>
    <article>
      <div aria-hidden="true" style="aspect-ratio:4/3;background:radial-gradient(circle at 63% 24%,#fff7ed 0 8%,transparent 8.5%),linear-gradient(171deg,transparent 57%,#7c2d12 57.3%),linear-gradient(182deg,transparent 67%,#431407 67.3%),linear-gradient(180deg,#b45309,#fcd34d 62%,#fff7ed)"></div>
      <div style="padding:1rem 1.1rem 1.2rem;display:grid;gap:.35rem">
        <h4 style="margin:0;font:700 1.15rem/1.2 var(--mv-font-sans);letter-spacing:-.01em">Napa Valley</h4>
        <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">California · 2 days · from $210</p>
        <p style="margin:.35rem 0 0;display:flex;flex-wrap:wrap;gap:.35rem"><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Vineyards</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Wine tasting</span></p>
      </div>
    </article>
    <article>
      <div aria-hidden="true" style="aspect-ratio:4/3;background:radial-gradient(circle at 74% 30%,#dbeafe 0 8%,transparent 8.5%),linear-gradient(178deg,transparent 57%,#1e3a8a 57.3%),linear-gradient(176deg,transparent 67%,#172554 67.3%),linear-gradient(180deg,#1d4ed8,#60a5fa 62%,#dbeafe)"></div>
      <div style="padding:1rem 1.1rem 1.2rem;display:grid;gap:.35rem">
        <h4 style="margin:0;font:700 1.15rem/1.2 var(--mv-font-sans);letter-spacing:-.01em">Algarve Coast</h4>
        <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">Portugal · 3 days · from $150</p>
        <p style="margin:.35rem 0 0;display:flex;flex-wrap:wrap;gap:.35rem"><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Surfing</span><span style="padding:.15rem .55rem;border-radius:999px;background:var(--mv-bg-muted);font-size:.75rem">Coastal trails</span></p>
      </div>
    </article>
    </mv-card-stack>
    <p id="cs-demo-out" style="margin:0;min-height:1.3em;font-size:.85rem;color:var(--mv-fg-muted)">Swipe the card, or use ←/→ on your keyboard (Backspace to undo).</p>
  </div>
  <div style="display:grid;gap:1rem;justify-items:center;align-self:center">
    <mv-card-stack label="Testimonials" autoplay="3800" no-controls style="--mv-card-stack-width:22rem;--mv-card-stack-offset:12px">
    <figure style="margin:0;padding:1.3rem 1.4rem;display:grid;gap:1rem;min-height:11rem;align-content:space-between">
      <blockquote style="margin:0;font-size:1rem;line-height:1.5">“We cut the time it takes to lay out our campaigns in half.”</blockquote>
      <figcaption style="display:flex;gap:.7rem;align-items:center"><span aria-hidden="true" style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#f472b6,#8b5cf6)"></span><span><strong style="display:block;font-size:.9rem">Maya Chen</strong><small style="color:var(--mv-fg-muted)">Art Director, Northwind Studio</small></span></figcaption>
    </figure>
    <figure style="margin:0;padding:1.3rem 1.4rem;display:grid;gap:1rem;min-height:11rem;align-content:space-between">
      <blockquote style="margin:0;font-size:1rem;line-height:1.5">“The components hold up in production, even in Safari.”</blockquote>
      <figcaption style="display:flex;gap:.7rem;align-items:center"><span aria-hidden="true" style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#34d399,#0ea5e9)"></span><span><strong style="display:block;font-size:.9rem">Daniel Okafor</strong><small style="color:var(--mv-fg-muted)">Lead front-end, Rails & Co</small></span></figcaption>
    </figure>
    <figure style="margin:0;padding:1.3rem 1.4rem;display:grid;gap:1rem;min-height:11rem;align-content:space-between">
      <blockquote style="margin:0;font-size:1rem;line-height:1.5">“Finally, animations that respect reduced motion.”</blockquote>
      <figcaption style="display:flex;gap:.7rem;align-items:center"><span aria-hidden="true" style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#fbbf24,#f97316)"></span><span><strong style="display:block;font-size:.9rem">Priya Raman</strong><small style="color:var(--mv-fg-muted)">Accessibility specialist</small></span></figcaption>
    </figure>
    </mv-card-stack>
    <p style="margin:0;font-size:.85rem;color:var(--mv-fg-muted)">autoplay · no-controls · pauses on hover</p>
  </div>
</div>
<script type="module">
  const stack = document.getElementById("cs-demo");
  const out = document.getElementById("cs-demo-out");
  const saved = [];
  stack.addEventListener("mv-swipe", (e) => {
    const name = e.detail.card.querySelector("h4").textContent;
    if (e.detail.direction === "right") saved.push(name);
    out.textContent = e.detail.direction === "right" ? `Saved to your wishlist: ${saved.join(", ")}` : `Skipped ${name}`;
  });
</script>

API

Attributes

NameTypDefaultDescription
labelstringAccessible name of the region.
accept-label / reject-labelstringLike / SkipText for the stamps, buttons and announcements.
no-loopbooleanSwiped cards leave the stack (data-empty + mv-empty at the end) instead of going back underneath.
no-controlsbooleanHides the Skip / Like buttons.
autoplaynumber (ms)Advances to the next card on an interval (4000 ms if empty); pauses on hover and focus, disabled under reduced motion.
thresholdnumber0.3Fraction of the width to cross to confirm a slow drag (a quick flick is enough).

Properties

NameTypDescription
currentElement | nullTop card.
remainingnumberCards still in the stack.

Methods

NameDescription
accept() / reject()Flings the top card right or left.
next()Tucks the top card under the pile with no verdict (used by autoplay).
undo()Brings back the last swiped card from the side it left.
reset()Puts every card back in its original order.

Events

NameDescription
mv-swipedetail: { direction: 'left' | 'right' | 'next', index, card }.
mv-emptyNo cards left (no-loop).

Content structure

NameDescription
childrenEach child is a card (role=group “card”, named after its title).

CSS classes

NameDescription
mv-card-stack-deck / -card / -tint / -stamp (-stamp-icon, -stamp-text) / -controls / -buttonGenerated parts; data-top on the top card, data-gone on cards that left the stack (no-loop), data-kind on buttons (reject | undo | accept).

CSS variables

NameDefaultDescription
--mv-card-stack-width20remCard width.
--mv-card-stack-offset14pxHow far the pile settles below the top card.
--mv-card-stack-tilt1Multiplier for the resting tilt of the cards below (0 = squared-up pile).
--mv-card-stack-radiusvar(--mv-radius-2xl)Card radius.
--mv-card-stack-accept / --mv-card-stack-rejectsuccess / dangerBadge, tint and button colors.

Accessibility

Region with aria-roledescription “card stack”. The stack is focusable: ← skip, → accept, Backspace or Ctrl/Cmd+Z undo (instructions exposed via aria-describedby). Only the top card is exposed; the others are aria-hidden and inert. Every verdict is announced (“I’m in: Lake Como. Next card: …”) in a polite live region. Named Skip / Undo / Like buttons; Undo is disabled until something was swiped. Reduced motion: instant flings, tucks and settles, no autoplay; dragging still works.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden