Starter Docs
UI

Card

A surface in shadcn's parts, plus link, selectable, tilt, spotlight, expandable and stacked cards. Every animation is CSS.

Review order

Check the details before you confirm.

Plan
Team, yearly
Seats
12
Billing email
billing@acme.co

Installation

Add the registry to components.json, once

components.json
{
  "registries": {
    "@rabtx": "http://localhost:3002/r/{name}.json"
  }
}

Add the component

npx shadcn@latest add @rabtx/card

Or, without the namespace, by URL:

npx shadcn@latest add http://localhost:3002/r/card.json

The files land in your components/ui and are styled by your own theme. Add --overwrite to replace a shadcn component with the same name.

Usage

Installed from the registry, import from @/components/ui/card; inside this monorepo, from @rabtx/ui/card.

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@rabtx/ui/card";

export function Invite() {
  return (
    <Card>
      <CardHeader>
        <CardTitle as="h3">Invite your team</CardTitle>
        <CardDescription>Teammates can view and edit every project.</CardDescription>
        <CardAction>
          <Button variant="ghost" size="sm">Later</Button>
        </CardAction>
      </CardHeader>
      <CardContent>…</CardContent>
      <CardFooter>
        <Button size="sm">Send invites</Button>
      </CardFooter>
    </Card>
  );
}

The parts are shadcn's, with the same data-slot names, so a shadcn card drops in. Each part pads itself on the inline axis, so any of them can be left out. CardAction pins to the top right of the header; the header is a container, and under 15rem wide the action drops below the title.

Media

An img, video, picture or anything with data-slot="card-media" placed directly in the card runs edge to edge and takes the card's corners when it is first or last.

Studio, Lisbon

Desks for 12 people, booked for the March offsite.

A card never takes focus itself. Put cardParts.target on the one link or button that owns it: the target's hit area stretches over the card, and the card answers hover (a stronger edge), press (a spring down to 0.985) and keyboard focus (the ring is drawn on the card), all through :has(). Other links and buttons in the card stay on top and keep working.

rabtx/ui

Components on native HTML and CSS.

<Card as="article">
  <CardHeader>
    <CardTitle as="h3">
      <a href="/repos/ui" className={cardParts.target}>rabtx/ui</a>
    </CardTitle>
  </CardHeader>
</Card>

Selectable

Make the target a label around a radio or checkbox. A click anywhere on the card picks it, and the checked input marks the card selected with a --primary edge. A disabled input dims the card and turns off its hover and press. data-selected, aria-pressed="true" or aria-current on the target select it too.

Plan

For one person and a side project.

$0

Shared projects and roles for up to 20.

$12

SSO and audit logs.

$40

Tilt

TiltCard leans toward a mouse or pen in 3D, up to max degrees at a corner (default 10), with a soft glare under the pointer, and springs back flat when the pointer leaves. Touch, reduced motion and data-motion="off" keep it flat.

Premium

Tilt me

The card leans toward the pointer, with a soft glare.

Steeper, no glare

max is 20 degrees at a corner.

Spotlight

SpotlightCard carries a soft light after the pointer and lights the part of the edge nearest to it. The light is --primary, so one token recolors a card. When a link or button inside has keyboard focus, the light rests along the top.

Edge functions

The light follows the pointer and lights the nearest edge.

Analytics

Recolored with one theme token on the card.

Expandable

ExpandableCard is a <details> element: the summary row is the toggle, so the browser provides the button role, the expanded state, Enter and Space, and opening for find-in-page. A shared name keeps one card of a group open, natively. The content grows to its height on the panel spring and the chevron turns over.

Can I change plans later?

Yes. Upgrades apply at once and are prorated; downgrades start with the next billing period.

Do you offer refunds?

Within 14 days of any charge, no questions asked. Write to billing and we reverse it.

Where is my data stored?

In the EU by default. Team and Scale plans can pin a region per project.

Stack

CardStack piles its cards behind the first, each a little smaller, and spreads them into a list. The toggle is a real button with aria-expanded; while collapsed its hit area covers the pile, so a click or tap anywhere opens it. A mouse over the pile fans the peeks a little further. Cards of similar height stack best.

  1. Deploy finished

    web, production, 42s ago

    now
  2. Ana commented

    “Ship it after the copy pass.”

    4m
  3. Invoice paid

    INV-2041, $1,840.00

    1h
  4. New sign-in

    Chrome on macOS, Lisbon

    3h

API

Every part takes everything its native element takes. Card adds:

Prop

Type

CardTitle takes as (div, h2 to h6); CardDescription is a p (or div); CardHeader and CardFooter can render header and footer, which is right inside an article or section card. TiltCard and SpotlightCard take the Card props, and TiltCard adds:

Prop

Type

ExpandableCard takes everything a <details> takes, plus:

Prop

Type

CardStack takes everything a <section> takes, plus:

Prop

Type

Outside React, cardClass() and cardParts from @rabtx/ui/card/styles give the class names:

<article class="rx-surface rx-card">
  <div class="rx-card-header">
    <h3 class="rx-card-title"><a class="rx-card-target" href="/a">Title</a></h3>
    <p class="rx-card-description">Description</p>
  </div>
</article>

Theming

The Card reads only the theme tokens.

PartTokens
Surfacefill --card, text --card-foreground, Flat edge --border
Floating depth--surface-ring, --surface-edge-top, --surface-edge-bottom, --surface-sheen, --surface-shade
Description--muted-foreground
Hover (link, selectable, expandable)edge --input, ring mixed toward --card-foreground
Selected--primary
Focus--ring
Tilt glare--card-foreground at 10%
Spotlight--primary
Corners--radius-box
Motion--spring-press, --spring-magnetic, --spring-panel, --spring-swap, --motion-ease

How the motion works

EffectCSS
Interactive card.rx-card:has(.rx-card-target) gets hover inside @media (hover: hover) and (pointer: fine), scale: 0.985 while the target is :active on the press spring, and the focus ring when the target is :focus-visible
Selected:has(.rx-card-target :checked) swaps the edge variables to --primary
Tiltthe pointer writes --_tilt-x and --_tilt-y; transform: perspective() rotateX() rotateY() transitions on the magnetic spring; the glare is a radial gradient at --_px / --_py under the content
Spotlighttwo pseudo-elements at --_px / --_py: a wide radial light inside, and a narrower one on a 1px padded layer whose content box is masked out (mask-composite: exclude), so only the edge lights
Expandableinterpolate-size: allow-keywords lets ::details-content transition block-size from 0 to auto on the panel spring, with content-visibility kept until it ends (allow-discrete)
Stackeach item is a one-row grid with its card aligned to the bottom (align-items: unsafe end); collapsed an item is 10px tall so its card overflows upward behind the one before, open it grows to auto with interpolate-size, and the cards scale back from 1 - i × 0.05
Chevronsrotate on the swap spring

JavaScript only writes the pointer position and the expanded state. The springs are fixed linear() curves: a tilt that is interrupted restarts from where it is.

Browser support

  • Chromium 131+: everything, including the expanding details and stack heights.
  • Safari and Firefox: cards, link and selectable cards, tilt and spotlight work; without interpolate-size and ::details-content the expandable card and the stack change height at once instead of animating.

Accessibility

  • A card adds no role and no tab stop. Use as="article" or a labelled section where the card is a unit of its own, and CardTitle as for the right heading level.
  • A clickable card is a real link, button or label inside it, so its name, role and keyboard come from that element. Name a link by its text, not "Read more".
  • ExpandableCard is native details and summary. The stack's toggle is a button with aria-expanded and aria-controls, and the cards behind are inert while collapsed.
  • Tilt and spotlight are decoration: they ignore touch, and reduced motion or data-motion="off" keeps the card flat and drops every fade and height animation.

On this page