Skip to content

Migration from vue-card-stack

vue-card-stack is unmaintained. Riffle is its spiritual successor: framework-agnostic at the core, with the same card-and-slot shape you already know from Vue.

vue-card-stack Riffle
cards cards
cardWidth, cardHeight cardWidth, cardHeight (now also accept 'auto')
sensitivity threshold
maxVisibleCards maxVisible
scaleMultiplier layout: fan({ scaleStep })
speed spring ('snappy', 'smooth', 'stiff', or { stiffness, damping })
stackWidth, paddingHorizontal, paddingVertical CSS on the container
#card="{ card }" slot unchanged

vue-card-stack turned scaleMultiplier into a per-card scale step of (1 - scaleMultiplier) / 10, and each card behind the front one shrank by that step: scale = 1 - step * depth. fan() uses the same shape, scale = 1 - scaleStep * depth, so the conversion is:

scaleStep = (1 - scaleMultiplier) / 10

The shipped default was scaleMultiplier: 0.5, which gives scaleStep = (1 - 0.5) / 10 = 0.05. Riffle’s own default is 0.08, a steeper fan, so pass the converted value to keep the old look:

scaleStepConversion.ts
/**
* The migration guide's worked conversion from vue-card-stack's
* `scaleMultiplier: 0.5` to Riffle's `fan({ scaleStep })`: shown on the
* migration page (apps/docs/src/content/docs/migration.mdx) via a `?raw`
* import, so the number in prose and the number that actually typechecks
* against `fan()` can never drift apart.
*/
import { fan } from '@rpxl/riffle'
export const layout = fan({ scaleStep: 0.05 })

The legacy README’s props table lists scaleMultiplier’s default as 0.75, but the published vue-card-stack@1.3.1 package actually defaults to 0.5, so if you never set the prop yourself, use 0.05 here, not the 0.025 the README’s table would imply.

Define layout once, outside the component or at the top of <script setup>, and pass it as :layout="layout": it is compared by identity, so a new fan() on every render reaches the engine as a change every time.

sensitivity to threshold: the fraction means a different distance

Section titled “sensitivity to threshold: the fraction means a different distance”

vue-card-stack’s sensitivity is the fraction of cardWidth + paddingHorizontal a card must travel before a drag commits. Riffle’s threshold is the fraction of one step’s travel, which for the default fan layout is cardWidth + gap. Both are a fraction of “cardWidth plus some spacing value,” so if you carry paddingHorizontal’s old value over as Riffle’s gap, sensitivity becomes threshold unchanged, same number. If the two spacing values differ, convert with:

threshold = sensitivity * (cardWidth + paddingHorizontal) / (cardWidth + gap)

using your old cardWidth and paddingHorizontal and your new gap. With both libraries’ defaults (cardWidth: 300 in each, paddingHorizontal: 20 in vue-card-stack, gap: 20 in Riffle), the two step sizes are already the same 320 pixels, so sensitivity: 0.25 and threshold: 0.25 behave identically without converting anything.

component (legacy)
import Vue from 'vue'
import VueCardStack from 'vue-card-stack'
export default {
components: {
VueCardStack,
},
data() {
return {
cards: [
{ background: '#00659d' },
{ background: '#00abbc' },
{ background: '#e2c58a' },
{ background: '#fc8890' },
{ background: '#b35d7f' },
],
}
},
}
template (legacy)
<vue-card-stack :cards="cards">
<template v-slot:card="{ card }">
<div :style="{ background: card.background }" style="width: 100%; height: 100%;"></div>
</template>
</vue-card-stack>

The same example translated directly: the same cards, the same #card slot, and the component renamed from vue-card-stack to Riffle. The slot’s card is typed straight from :cards="cards", so no cast is needed to read card.background.

LegacyMigration.vue
<!--
The legacy vue-card-stack README's usage example, translated directly to
Riffle: the same cards, the same #card slot. The migration guide shows this
file as its "after". `card` here is typed `LegacyCard` straight from
`:cards="cards"`, Riffle's generic `<Riffle>`, no cast needed.
-->
<template>
<Riffle :cards="cards">
<template #card="{ card }">
<div :style="{ background: card.background }" style="width: 100%; height: 100%"></div>
</template>
</Riffle>
</template>
<script setup lang="ts">
import { Riffle } from '@rpxl/riffle/vue'
interface LegacyCard {
background: string
}
const cards: LegacyCard[] = [
{ background: '#00659d' },
{ background: '#00abbc' },
{ background: '#e2c58a' },
{ background: '#fc8890' },
{ background: '#b35d7f' },
]
</script>

This keeps Riffle’s defaults, which differ from the legacy ones in two places: maxVisible is 4 (legacy maxVisibleCards was 10), and the fan’s scaleStep is 0.08 (the shipped legacy default converts to 0.05, above). Pass :max-visible and :layout to match the old look exactly.

npm uninstall vue-card-stack
npm install @rpxl/riffle

Next: Adapter details documents every export the snippet above uses.