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…

import * as React from "react"
import { cn } from "@/lib/utils"

export type CornerRibbonCorner = "top-right" | "top-left"
export type CornerRibbonTone = "primary" | "accent" | "destructive"

/**
 * tone → ribbon fill + readable foreground, kept as one editable data table.
 * primary/destructive ride existing token utilities; accent has no dedicated
 * utility class in this theme, so it's resolved via color-mix against
 * var(--chart-2) instead of a hardcoded color.
 */
const TONE_CLASSES: Record<CornerRibbonTone, string> = {
  primary: "bg-primary text-primary-foreground",

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/corner-ribbon.json

Prompt

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-hidden does 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-mix against var(--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 shadcn Card) passes straight through via className.

On This Page