Display
Corner Ribbon
A diagonal corner badge draped over a card — NEW / SALE / POPULAR callouts that wrap any card content.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/corner-ribbon.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "CornerRibbon" component (no runtime
dependencies beyond React and the shared cn() utility).
Contract
- Export a forwardRef<HTMLDivElement, CornerRibbonProps> extending
React.HTMLAttributes<HTMLDivElement>.
- Props: label: string (required — the ribbon text, e.g. "SALE");
corner?: "top-right" | "top-left" (default "top-right");
tone?: "primary" | "accent" | "destructive" (default "primary");
children: React.ReactNode (the card content the ribbon decorates);
className merges onto the root via cn(), remaining props spread on it.
Behavior
- The root is the outer card wrapper itself — relative + overflow-hidden —
so the ribbon's two tails clip cleanly on the card's own edges instead of
needing a separate clipping frame.
- The ribbon is a single absolutely-positioned strip, fixed width (~10rem),
rotated +45deg for "top-right" and -45deg for "top-left", nudged past the
corresponding edge so its visible band centers over the corner.
- No hooks, no state, no browser APIs — pure CSS — so the component never
gets a "use client" directive and stays renderable on the server.
- The label is plain, readable text (no aria-hidden): it's information
("SALE"), not decoration, so it must reach the accessibility tree.
- The ribbon itself is pointer-events-none — it decorates the corner, it
never intercepts clicks meant for the card underneath.
Rendering & styling
- Semantic tokens only, tone resolved through one small lookup table:
primary → bg-primary text-primary-foreground; destructive → bg-destructive
text-background; accent → a color-mix against var(--chart-2) (this theme
has no dedicated accent-foreground token, so accent and destructive both
read text-background for contrast).
- Ribbon text: text-xs font-semibold uppercase tracking-wide, centered.
- cn() merges className onto the root; the root carries no card chrome of
its own (no border/radius/background) — that's the consumer's job via
className, so the ribbon works over any children.
Customization levers
- Tone mapping: add a tone by extending the lookup table with one more
bg-*/text-* pair (or a color-mix expression) — nothing else changes.
- Ribbon size: the fixed w-40 strip width and py-1 thickness are the two
knobs for a bigger or thinner band; widen it for long labels, thin it for
a subtler badge.
- Angle: the ±45deg rotation pairs with the offset that centers the strip —
changing the angle means re-tuning the corner offset to match.
- Pairs with Card: pass className="rounded-xl border bg-card" (or a shadcn
Card's own classes) straight onto CornerRibbon — since overflow-hidden
already lives on the root, the ribbon clips to whatever radius the
consumer gives it.Concepts
- Diagonal-ribbon geometry — one rotated strip, no SVG or extra clipping frame: the parent's own
overflow-hiddendoes the clipping, so the recipe stays a single absolutely-positioned span. - Clip-on-overflow-hidden — the ribbon deliberately overshoots both edges before rotation; the card boundary (not a fixed corner frame) is what trims the tails, so it scales to any card size.
- Tone-to-token mapping — tone never touches a hardcoded color; primary and destructive ride existing token utilities, accent is resolved via
color-mixagainstvar(--chart-2)since this theme has no dedicated accent-foreground token. - Wrapper, not a card — the root adds no border, radius or background of its own; it only supplies
relative overflow-hidden, so any card styling (or a shadcnCard) passes straight through viaclassName.