
Corner Sale Ribbon for Product Cards
--rib-01-size value drives the corner box, the band length and the offset, so the same ribbon works on a 200px thumbnail and a 600px hero. A one-shot sheen sweeps the band on load and replays on hover.Published
20 hand-coded CSS ribbons for e-commerce product cards, SaaS pricing tables, blog thumbnails, open-source project pages, and coupon campaigns. Covers corner sale and sold-out bands with wrapped tails, the GitHub fork-me ribbon rebuilt without an image, swallowtail V-cut banners, most-popular pricing bands, award rosettes, certificate seals, coupon tear-off edges, and a single-element clip-path build. 16 are pure CSS with no JavaScript. Scoped under .rib-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related30 CSS Badges24 CSS Tags & Chips18 CSS Product Cards21 CSS Pricing Tables

--rib-01-size value drives the corner box, the band length and the offset, so the same ribbon works on a 200px thumbnail and a 600px hero. A one-shot sheen sweeps the band on load and replays on hover.Published

:has() selector re-styles the entire card at once: the photo desaturates and dims, the price greys, the button disables, and a frosted translucent band slides across the corner with backdrop-filter so the product stays visible underneath — the shopper can still see what they missed. Zero JavaScript; the checkbox is the state machine.Published

rotate.Published

position: fixed that breaks in a scroll container. A single anchor, two dashed stitch lines drawn with border, an inline SVG glyph, and a hover state that pushes the whole band a couple of pixels further out of the corner. Left and right variants share one rule set — the side is a custom property.Published

clip-path, and the label sits in the negative space as a separate tab with a real gap around it, so the badge reads as slotted through the card rather than printed on it. One registered @property length drives the notch and the tab together, which makes a clip-path chamfer animatable — hover a card and the corner opens. Underneath, freshness is a quantity, not a sticker: a hairline meter whose length and colour both interpolate from one number.Published

clip-path polygon cuts both ends, and because every point is written against a single --rib-06-v property, notch depth is one number you can theme, animate, or shrink on mobile. The band genuinely overhangs the sheet and its fold tails attach flush to the outer ends, so it reads as tied around the page — the detail that separates a wrapped ribbon from a coloured rectangle with decorations near it. Flat ink, no gloss: the only shading is a 3px darker edge that the clip follows.Published

perspective, animated hazard stripes scrolling behind the headline, and a neon edge that bleeds through the layers. The key move is the counter-skew — the band is skewed, the text inside is skewed back by the same angle, so the type stays upright and readable while the shape stays aggressive.Published

:has() lifts the whole row's shadow when any card is hovered, the featured card is elevated in the grid with a negative block margin, and the ribbon text is bound to the plan with aria-describedby so it is announced, not just seen.Published

:has() — the entire state machine is the checked radio, so it works with no JavaScript at all and is keyboard-navigable by default.Published

clip-path make the shape, one --rib-10-notch variable controls the bite, and a companion price tag shows the other half of the technique — a punched hole made by subtracting a circle with mask-composite. Selecting a flag is pure CSS: hidden checkboxes and :has().Published

aria-pressed (not a class), and the whole animation is transform-origin: top center plus five keyframes.Published

clip-path and a repeating-conic-gradient, with two ribbon tails dangling beneath and a slow specular sweep across the metal. Every value is derived — the scallop polygon comes from trigonometry, the pleats from one repeating gradient — so it stays sharp at any size and weighs nothing.Published

@property <angle> rotates a multi-stop conic gradient so the gold genuinely catches light as it turns — an effect that is impossible with an unregistered custom property, because gradients cannot be transitioned but their angle variable can. Sits on a real certificate layout with an engraved guilloche border.Published

preserve-3d space — a flat face, two wings creased back on the Y axis, and two tails folded down on the X axis — so the ends genuinely turn away from the camera instead of being triangles pointing at it. Each facet is lit by the same light source, its value derived from the accent with color-mix(), and a blurred contact shadow plants the whole strip on the card. It unfolds on load from a hinge at its pinned edge and settles at three degrees, never quite flat.Published

mask layers rather than covered with background-coloured circles, so the coupon sits correctly on any page colour, gradient or photograph. Copying the code is one button with proper success feedback.Published

outline-offset, a woven twill pattern from two crossed repeating gradients, real paper grain from an inline SVG turbulence filter, and a slightly tilted seat so it looks pinned rather than pasted.Published

@property percentage lives inside the clip-path, which is the only way to transition a polygon smoothly. Keyboard users get the same reveal through :focus-within.Published

popover API, so it lives in the top layer above everything without a z-index war. The entry animation uses @starting-style and transition-behavior: allow-discrete, the pair that finally makes it possible to animate an element in and out of display: none with pure CSS.Published

animation-delay, so a wave travels along the cloth. Shading is part of the animation — every slice brightens as it rises and darkens as it falls — which is what turns a row of moving rectangles into fabric catching light.Published

clip-path, no pseudo-elements, no wrappers, no overflow tricks. Every coordinate is a registered @property, so you can drag the sliders and watch the polygon interpolate live, then copy the exact CSS the playground produces.Published
position: relative and overflow: hidden, then absolutely position the ribbon and rotate it 45 degrees. Make it roughly 1.5× wider than the corner it crosses so the ends run past the card edge and get clipped, rather than stopping short and revealing that it is a rectangle. Second, the tails — the small darker triangles that appear to fold behind the card — are what sell the wrap. Build them with ::before and ::after using border-color: transparent triangles or a small clip-path, filled with a darker shade of the ribbon colour derived via color-mix(in oklch, var(--ribbon) 70%, black). Third, the shadow: a tight filter: drop-shadow() follows the actual ribbon shape including its clipped edges, whereas box-shadow draws a rectangle around the unrotated box and looks wrong. One trap worth knowing: overflow: hidden on the card is what clips the ribbon ends, but it also clips any tooltip or dropdown inside that card — if you need both, use overflow: clip on the axis you actually want clipped.<a> positioned fixed (or absolute within a relatively positioned hero) at the corner, rotated 45 degrees, given generous horizontal padding so the band extends past both card edges, with the text centred inside. Two details make it read as authentic: the thin light border inset from each long edge — a single box-shadow: inset 0 1px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.2) handles both — and a slight letter-spacing on uppercase text, which is what the original bitmap had. For accessibility, this is a real link, so it needs discernible text: the visible label "Fork me on GitHub" is usually enough, but if you shorten it to an icon, add an aria-label. Also keep the tap target at least 44 by 44 CSS pixels — a thin rotated band is easy to make too small to hit on mobile.clip-path: polygon() lets you cut the entire ribbon silhouette — including the V-notch or swallowtail ends — from one element, with no pseudo-elements at all. A basic banner with notched ends is clip-path: polygon(0 0, 100% 0, calc(100% - 18px) 50%, 100% 100%, 0 100%, 18px 50%): the two mid-edge points pull each end inward to a point. That is the whole shape. The advantages over the classic pseudo-element build are real — one element instead of three, no z-index juggling for the tails, and the shape animates smoothly because clip-path is interpolatable between polygons with the same number of points. Two caveats decide when to use it. First, clip-path clips everything in the element including its own ::before and ::after, so decorative extras have to live on a wrapper — this catches people out constantly. Second, a clipped element cannot cast a box-shadow, because there is no box left to shadow; use filter: drop-shadow() on the parent instead, which follows the clipped silhouette. Demo 20 is built this way as the reference.overflow: visible and enough padding-block-start that the ribbon does not collide with the plan name — a common bug when the card is inside a grid with align-items: stretch. Give the featured card a slight scale(1.03) and a stronger shadow so the ribbon reinforces a hierarchy the layout already establishes rather than doing the work alone. On semantics: the ribbon is real information, not decoration, so it belongs in the document as text — not as a ::before with content: "Most Popular", which screen readers may skip and which no user can select or translate. Generated content is fine for the ribbon's tails; it is wrong for its label. If the card is a radio-selectable plan, wire the highlight from state with .card:has(:checked) so the ribbon follows the actual selection instead of being hardcoded to one plan.mask-image: radial-gradient(circle 8px at 8px 50%, transparent 98%, black 100%) with mask-size set to the notch spacing and mask-repeat: repeat-y — each repeat punches a semicircular bite out of the edge. Because it is a mask rather than an overlay, the notches are genuinely transparent, so whatever sits behind the coupon shows through; an overlay of background-coloured circles looks identical on a flat background and breaks instantly on a gradient or photo. Pair it with a border-left: 2px dashed on the tear line. Two production notes. Safari still wants the -webkit-mask-image prefix, and it is one extra line to keep the effect from vanishing entirely there. And if the coupon is interactive — a copy-code button, a claim action — that control must be a real <button>, not the whole card, so keyboard users get a single predictable target rather than a large ambiguous one.::before / ::after. Three further requirements. Never rely on colour alone: a greyed-out card means nothing to a colour-blind user, so "Sold Out" must appear as text, and the underlying control should be genuinely disabled or replaced with an enabled alternative like "Notify me" — a dimmed-but-clickable button is worse than no signal at all. Contrast still applies to rotated text: a 45-degree band does not exempt it from the 4.5:1 requirement, and light-on-bright-red is the usual failure. Finally, rotation must not be the only cue for meaning, and any animated ribbon — the unfold, the waving flag, the slide-in notification — needs a prefers-reduced-motion: reduce guard that settles it into its final visible state rather than removing it..rib-NN rules and they behave identically anywhere, because there is no JavaScript to place in a lifecycle. In component terms, the natural shape is a small presentational component taking a label and a variant: <Ribbon label="Sale" variant="corner" />, with the colour driven by a CSS custom property so themes stay in CSS rather than in props. The four demos with JavaScript — the hanging bookmark toggle, the coupon copy action, the slide-in notification, and one hover enhancement — wire into the usual hooks: useEffect with a returned cleanup in React, onMounted plus onBeforeUnmount in Vue, onMount returning cleanup in Svelte, and 'use client' for those four in the Next.js App Router while the other 16 render fine as server components. For Tailwind v4, padding, colour and positioning map to utilities directly; the parts with no utility equivalent — clip-path: polygon(), mask gradients, keyframes — belong in a @theme block or an arbitrary-value class. Each ribbon is roughly 1-3KB of CSS.30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.
20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
25 hand-coded CSS blockquote designs for SaaS testimonials, editorial pull quotes, documentation callouts, and developer engineering blogs: large decorative quotation marks with ::before / ::after glyphs, Tailwind CSS blockquote component, pure CSS callout / admonition boxes for Docusaurus / Mintlify / Nextra documentation, responsive center-aligned quote banners, modern minimalist left-border editorial style, animated CSS gradient border, classic left border, brutalist high-contrast, glassmorphism frosted card, glowing neon border, article pull quote with text-wrap, speech bubble chat-style, thick vertical accent rail, customer testimonial cards with avatar and star rating, inline highlighted text, marker highlight underline, floating drop-shadow, aurora gradient background, Twitter / X card style, multi-column newspaper editorial, IDE code-comment style, Markdown-compatible defaults, JavaScript testimonial quote slider, expandable read-more, and one-click copy button. 22 Pure CSS + 3 Light JS (slider, expand, copy). Every design scoped under .bq-NN prefix for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, WCAG 2.2 AA accessible, framework-agnostic (React, Vue, Svelte, Astro, Next.js, Nuxt, Remix, SvelteKit).