Sticker Media Card
The poster, not the sleeve. Artwork fills the die-cut frame edge to edge, the caption sits outside it on the backing, and the whole tile is one focusable link.
SurfacesClient component@radix-ui/react-slot
Preview
Installation
The CLI writes the source into your project and pulls in whatever it depends on.
$
pnpm dlx shadcn@latest add @duck/sticker-media-cardPulls in @duck/theme, @duck/sticker-progress. Already installed items are skipped.
Usage
import { StickerMediaCard } from "@/components/ui/sticker-media-card"The file lands in components/ui/sticker-media-card.tsx and belongs to you from that point on. Edit it in place rather than wrapping it.
Props
Everything not listed here is forwarded to the underlying element.
| Prop | Type | Default | Description |
|---|---|---|---|
| src | string | - | Artwork URL. Loads lazily. |
| alt | string | "" | Alternative text. Used as the link name only when there is no title, since the caption would otherwise say it twice. |
| aspect | "2/3" | "16/9" | "1/1" | number | "2/3" | Frame ratio. A number is width / height. |
| title | React.ReactNode | - | First line of the caption, below the frame. |
| subtitle | React.ReactNode | - | Second caption line, for a year and a runtime: "2016 · 1h 35m". |
| href | string | - | Where the tile goes. Inherited from the anchor. |
| asChild | boolean | false | Render a router Link as the tile. Pass exactly one element child and no children of its own. |
| overlay | React.ReactNode | - | Centred decoration that fades in with a scrim on hover and focus — a play badge. Takes no pointer events. |
| progress | number | - | 0 to 100. Draws StickerProgressTrack at size="sm" along the bottom edge of the artwork, aria-hidden so its value stays out of the link's name. |
| fallback | string | - | Text drawn on a gradient when src is missing or the image fails. Defaults to alt. |
Rules
What keeps this component from turning into noise.
- The tile is one link, so nothing inside it may be interactive. A play button in the frame doubles every tab stop in the grid and hides the real target.
- progress is a readout, not a scrubber. Seeking belongs to the player, not the poster. It is also aria-hidden — how far in belongs in the tile's own name, not in a nested progressbar.
- One aspect per shelf. Mixed ratios in the same row break the grid before they add anything.
- Give it href or asChild — a tile with neither is a box nobody can reach.


