# Flick Cards Slider

> A fanned deck of photo cards you flick sideways: the next card swings to the front with one elastic overshoot, the rest dim as they fan out, and the front card's button fades in last.

- Page: https://ui.devigner.cc/components/flick-cards-slider
- Category: Marketing
- Requirements: React 18 or 19, Tailwind CSS v4
- Install: `npx devignerui add flick-cards-slider` (writes components/ui/flick-cards-slider.tsx)
- Packages it needs: cn, motion, devignerui

## What the demo shows

The live demo is a fan of five photo collections, Ember, Drift, Grain, Tide and Noir. Drag the front card sideways and the deck follows your pointer; let go past a short distance and the next card swings to the front with one overshoot while the others dim, and its View collection button fades in.

## When to use it

- A small set of products, drops or campaigns, three to eight, where each one is a photo with a name and one call to action.
- A hero or launch section that should invite a playful drag rather than a click on arrows.

## When to reach for something else

- Long catalogs or anything people need to scan or compare: only the front card is readable. Use a grid.
- Content that is mostly text. The cards are photos first, with a single large title over them.

## Usage

```tsx
import { FlickCardsSlider } from "@/components/ui/flick-cards-slider";

<FlickCardsSlider items={products} />
```

Custom composition:

```tsx
<FlickCardsSlider items={products} index={index} onIndexChange={setIndex} className="w-120" />
```

### Product drop with links

Each card links to its product page; the deck is named for screen readers.

```tsx
import { FlickCardsSlider, type FlickCardsSliderItem } from "@/components/ui/flick-cards-slider";

const products: FlickCardsSliderItem[] = [
  { image: "/drops/kr90.jpg", title: "KR90", action: { label: "Get this product", href: "/shop/kr90" } },
  { image: "/drops/nv300.jpg", title: "NV300", action: { label: "Get this product", href: "/shop/nv300" } },
  { image: "/drops/lx45.jpg", title: "LX45", action: { label: "Get this product", href: "/shop/lx45" } },
];

export function Drop() {
  return (
    <section className="grid place-items-center overflow-hidden py-24">
      <FlickCardsSlider aria-label="New arrivals" items={products} className="w-80" />
    </section>
  );
}
```

### Follow the front card

Control the index to show details for whichever card is in front.

```tsx
import { useState } from "react";
import { FlickCardsSlider, type FlickCardsSliderItem } from "@/components/ui/flick-cards-slider";

export function Lookbook({ looks }: { looks: (FlickCardsSliderItem & { notes: string })[] }) {
  const [index, setIndex] = useState(0);
  return (
    <div className="flex flex-col items-center gap-10 overflow-hidden py-16">
      <FlickCardsSlider items={looks} index={index} onIndexChange={setIndex} />
      <p className="text-sm text-muted-foreground">{looks[index]?.notes}</p>
    </div>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| items | `FlickCardsSliderItem[]` | - | The cards, in order: { image, alt?, title?, action? }. action is { label, href?, onClick? }: a link with href, else a button. |
| index | `number` | - | Controlled front card, counted from 0. Pass with onIndexChange. |
| defaultIndex | `number` | 0 | Front card on first render when uncontrolled. |
| onIndexChange | `(index: number) => void` | - | Called with the new front card after a flick or an arrow key. |
| classNames | `FlickCardsSliderClassNames` | - | Slot classes: card (every photo frame), title (the centered label) and action (the button). |
| className | `string` | w-72 | Classes on the deck. Its width is the front card's width; the side cards fan out past it, and the title, button and corners scale with it. |

## Keyboard

| Keys | Action |
| --- | --- |
| Tab | Focuses the deck, then the front card's button. |
| Left arrow | Brings the card on the left to the front, looping past the first card. |
| Right arrow | Brings the card on the right to the front, looping past the last card. |

## Accessibility

- The deck is a region with aria-roledescription="carousel" and the label "Cards"; pass aria-label to name it, such as "New arrivals".
- Each card is a group with aria-roledescription="slide" and a label like "2 of 5". Only the front card is exposed: the others are aria-hidden and their buttons leave the tab order and take no clicks.
- A polite live region reads the new front card, its title when the title is a string, after each flick or key press.
- Give each item an alt when the photo carries meaning the title doesn't.
- Honors prefers-reduced-motion and an ancestor <MotionConfig reducedMotion="always">, so an in-app motion switch works too.

## Theming

- The deck's width is the front card's width (w-72 by default) and the card is always 2:3. The title, the button and the corner radius scale with that width through container query units, so className="w-96" grows everything in proportion.
- Cards one and two away from the front are dimmed by a layer of the background token at 27.5% and 52.5%. On a dark background they darken; on a light one they fade toward white.
- The button uses background and foreground, the title is white over the photo. Change them through classNames.action and classNames.title.
- The front card carries data-front, so classNames.card can style it alone, such as data-front:shadow-2xl.
- Cards move on TWEEN_FLICK, GSAP's elastic.out(1.2, 1) over 0.6s; the dim layer and a leaving button use TWEEN_FLICK_FADE (0.2s) and the arriving button TWEEN_FLICK_REVEAL (0.7s).

## Edge cases

- The side cards fan out about one card width past each edge of the deck. Leave room for them, or clip the section with overflow-hidden, or a narrow page scrolls sideways.
- A full flick is two deck widths of drag; letting go past a tenth of that turns the deck, anything shorter springs back. Beyond one deck width the drag meets resistance.
- The deck loops in both directions. With more than five items the cards three or more away from the front are hidden and slide in as they come round.
- A press that moves less than 4px is a click, so the front card's button works under the drag. A drag that starts on the button never clicks it.
- On touch the deck takes horizontal drags only, so the page still scrolls vertically over it.

## Troubleshooting

**The page scrolls sideways on phones.**
The side cards fan out past the deck's edges. Put the deck in a section with overflow-hidden, or make it narrower so the fan fits.

**Dragging the deck selects text or drags the image instead.**
The deck turns off text selection and native image dragging on its own cards. If you wrap it in something draggable, stop that wrapper from starting its own drag on pointerdown.

**The component renders with no background or the wrong colors.**
Colors use the standard shadcn/ui tokens only (background, foreground, primary, secondary, muted, accent, border, input, ring, destructive), so a project set up with shadcn/ui needs nothing extra and the component follows its theme, dark mode included. Without shadcn/ui, define those CSS variables for :root and .dark and map them in your Tailwind v4 @theme, or run npx shadcn init.
