Design system

Card Loader

A stack of three cards that cycles while something takes a few seconds to load.

import { CardLoader } from "@/ui";

Card Loader matches the Figma Card Loader Animation. Use it to fill a panel while something takes several seconds, like building an analysis plan, by showing short tips about the product. In the app it is shown in the workbook chat's plan panel while the plan is getting ready and while modifications are applied.

Default

Three cards are stacked, the front one largest. Every 3 seconds the front card drops to the back and the next card grows into its place. The rail on the left shows which card is in front. The buttons on the right go to the previous card, pause or resume, and go to the next card.

<CardLoader
  cards={[
    { title: "Understanding your Data", description: "See what data is available...", image: dataPreview },
    { title: "Understanding Key Definitions (KDs)", description: "Standardize how metrics...", image: kdPreview },
    { title: "Petavue Visualization", description: "Create trustworthy charts...", image: vizPreview },
  ]}
/>

The first card in cards starts in front. Each card needs a unique title.

With a status line

Card Loader doesn't say what is loading. Put a short status line with a Spinner under it, as the workbook chat does.

<CardLoader cards={tips} />
<div className="status">
  <Spinner size={16} />
  Getting ready...
</div>

Without images

A card without an image shows a Grey 100 block in its place. Use it for a draft before the screenshots exist. This example also sets interval to 4 seconds.

<CardLoader cards={tipsWithoutImages} interval={4000} />

Anatomy

PartSizeStyle
Frame500 × 378pxShrinks to its container's width
Front card400px wide, 40px from the topWhite, image 234px
Next card364px wide, 20px from the topWhite, 269px tall, image 163px
Back card304px wide, at the topGrey 50, 206px tall, image hidden behind a Grey 100 block 100px tall
Card12px padding, 10px gap1px Grey 100 border, 8px radius, 0 8px 4px shadow at 8%
Title16px / 26px, medium--color-text-primary, one line, cut off with an ellipsis
Description12px / 19px--color-text-secondary
Progress rail6px wide, 6px gapPrimary 100 dots; the current card is a 60px Primary 500 bar
Buttons32px round, 8px gapGrey 50 fill, 1px Grey 100 border, 12px Grey 600 icon; Grey 100 on hover

Card images are anchored to the top left and cropped to fill the space, so the top left of a screenshot is what shows. Use screenshots about 2:1 wide.

Cards move with a 0.6s ease. With reduced motion turned on, they switch without moving.

Props

PropTypeDefaultNotes
cards{ title, description, image? }[][]Three cards. Nothing renders when it is empty.
intervalnumber3000Milliseconds each card stays in front.
labelstring"Product tips"Accessible name of the carousel.
classNamestringOther props are passed to the root too.

Card Loader keeps its own place and pause state. It starts again from the first card each time it mounts.

Differences from Figma

  • Figma has no state after Pause is pressed. Here the Pause button turns into a Play button.
  • Figma has no hover state for the buttons. Here they turn Grey 100.
  • The back card's fill is Figma's #FCFCFC, which isn't a token. It uses Grey 50 (#F8F9FC).

Accessibility

  • The root is a section with aria-roledescription="carousel" and the label as its name. Only the front card is exposed to screen readers; the other two are aria-hidden.
  • While cards change by themselves, changes aren't announced. Once paused, Previous and Next announce the new front card.
  • The Pause button stops the cycle, which people need for anything that moves for more than 5 seconds. Keep it.
  • Images are decorative (alt=""). The title and description carry the content.

Do and don't

  • Do keep each description to one or two lines.
  • Do use it only for waits of several seconds. For a short wait, use a Spinner or Skeleton.
  • Don't use it for content people need to read to continue. It cycles on its own.
  • Don't use more or fewer than three cards. The stack is drawn for three.

On this page