20 CSS Ribbons03 / 20
Bestseller Corner Ribbon
A corner ribbon that is tied on rather than printed on. Demo 01 clips its band flush to the card; this one crosses the top-left corner at 45° and keeps going, overhanging both card edges, with two darker triangles folding under the card where a real ribbon would disappear. Overhang plus fold is what makes it read as a physical band. Gold-foil gradient, slow animated shine, and a tilt that deepens on hover — all from two pseudo-elements and one rotate.
Published
The code
<section class="rib-03" aria-label="Featured product bestseller ribbon demo">
<div class="rib-03__stage">
<article class="rib-03__card">
<div class="rib-03__ribbon" aria-hidden="true"><span class="rib-03__label">BESTSELLER</span></div>
<div class="rib-03__media">
<img class="rib-03__img" alt="Black and silver over-ear studio headphones on a near-black backdrop" loading="lazy" decoding="async" width="640" height="520"
src="https://images.unsplash.com/photo-1487215078519-e21cc028cb29?q=80&w=800&auto=format&fit=crop">
</div>
<div class="rib-03__body">
<p class="rib-03__rank">Bestseller · #1 in Studio Headphones</p>
<h3 class="rib-03__name">Auralis One Reference</h3>
<p class="rib-03__stars"><span class="rib-03__starrow" aria-hidden="true">★★★★★</span><span class="rib-03__rate">4.9</span><span class="rib-03__count">2,418 reviews</span></p>
<p class="rib-03__price">$249<span class="rib-03__unit">or $21/mo</span></p>
<button class="rib-03__cta" type="button">View product</button>
</div>
</article>
<p class="rib-03__chip" aria-hidden="true">corner wrap · rotate:-45deg · S×√2 + overhang · clip-path fold tails · foil sweep</p>
</div>
</section><section class="rib-03" aria-label="Featured product bestseller ribbon demo">
<div class="rib-03__stage">
<article class="rib-03__card">
<div class="rib-03__ribbon" aria-hidden="true"><span class="rib-03__label">BESTSELLER</span></div>
<div class="rib-03__media">
<img class="rib-03__img" alt="Black and silver over-ear studio headphones on a near-black backdrop" loading="lazy" decoding="async" width="640" height="520"
src="https://images.unsplash.com/photo-1487215078519-e21cc028cb29?q=80&w=800&auto=format&fit=crop">
</div>
<div class="rib-03__body">
<p class="rib-03__rank">Bestseller · #1 in Studio Headphones</p>
<h3 class="rib-03__name">Auralis One Reference</h3>
<p class="rib-03__stars"><span class="rib-03__starrow" aria-hidden="true">★★★★★</span><span class="rib-03__rate">4.9</span><span class="rib-03__count">2,418 reviews</span></p>
<p class="rib-03__price">$249<span class="rib-03__unit">or $21/mo</span></p>
<button class="rib-03__cta" type="button">View product</button>
</div>
</article>
<p class="rib-03__chip" aria-hidden="true">corner wrap · rotate:-45deg · S×√2 + overhang · clip-path fold tails · foil sweep</p>
</div>
</section>.rib-03,
.rib-03 *,
.rib-03 *::before,
.rib-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rib-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--rib-03-bg: #14120f;
--rib-03-bg: oklch(0.19 0.014 75);
--rib-03-card: #221f1a;
--rib-03-card: oklch(0.26 0.016 78);
--rib-03-ink: #f7f3ec;
--rib-03-ink: oklch(0.97 0.012 85);
--rib-03-mut: #a8a096;
--rib-03-mut: oklch(0.72 0.018 82);
--rib-03-acc: #e2b357;
--rib-03-acc: oklch(0.81 0.14 82);
--rib-03-cut: 118px;
--rib-03-band: 38px;
--rib-03-over: 17px;
--rib-03-fold: 14px;
--rib-03-top: 30px;
--rib-03-len: calc(var(--rib-03-cut) * 1.415 + var(--rib-03-over) * 2);
--rib-03-r: 22px;
background: var(--rib-03-bg);
color: var(--rib-03-ink);
font-family: system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
-webkit-font-smoothing: antialiased;
}
.rib-03__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(18px,3vw,28px);
padding: clamp(24px,6vw,64px);
background: radial-gradient(90% 70% at 50% 8%,color-mix(in oklch,var(--rib-03-acc) 13%,transparent),transparent 66%);
}
.rib-03__card {
position: relative;
width: min(100%,376px);
overflow: visible;
border-radius: var(--rib-03-r);
background: var(--rib-03-card);
border: 1px solid oklch(1 0 0/.09);
box-shadow: 0 30px 70px -28px oklch(0 0 0/.85);
transition: translate .45s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover {
translate: 0 -5px;
}
.rib-03__card:hover .rib-03__ribbon {
rotate: -47deg;
translate: -2px -2px;
}
.rib-03__ribbon {
position: absolute;
z-index: 3;
inset-block-start: var(--rib-03-top);
inset-inline-start: calc((var(--rib-03-len) - var(--rib-03-cut)) / -2);
inline-size: var(--rib-03-len);
min-block-size: var(--rib-03-band);
display: grid;
place-items: center;
rotate: -45deg;
transition: rotate .5s cubic-bezier(.16,1,.3,1),translate .5s cubic-bezier(.16,1,.3,1);
background: #e2b357;
background: linear-gradient(100deg,color-mix(in oklch,var(--rib-03-acc) 72%,black 6%) 0%,color-mix(in oklch,var(--rib-03-acc) 100%,white 22%) 34%,var(--rib-03-acc) 52%,color-mix(in oklch,var(--rib-03-acc) 74%,black 8%) 100%);
background-size: 220% 100%;
box-shadow: 0 10px 24px -10px oklch(0 0 0/.7),inset 0 1px 0 oklch(1 0 0/.45),inset 0 -1px 0 oklch(0 0 0/.22);
animation: rib-03-foil 7s ease-in-out infinite;
}
@keyframes rib-03-foil {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.rib-03__label {
padding: 0 16px;
font-size: 12px;
font-weight: 800;
letter-spacing: .18em;
line-height: 1;
color: oklch(0.24 0.03 78);
text-shadow: 0 1px 0 oklch(1 0 0/.32);
}
.rib-03__ribbon::before,
.rib-03__ribbon::after {
content: '';
position: absolute;
inset-block-start: 100%;
inline-size: var(--rib-03-over);
block-size: var(--rib-03-fold);
background: #8a6a25;
background: color-mix(in oklch,var(--rib-03-acc) 58%,black);
z-index: -1;
}
.rib-03__ribbon::before {
inset-inline-start: 0;
clip-path: polygon(0 0,100% 0,100% 100%);
}
.rib-03__ribbon::after {
inset-inline-end: 0;
clip-path: polygon(0 0,100% 0,0 100%);
}
.rib-03__media {
aspect-ratio: 5/4;
border-radius: var(--rib-03-r) var(--rib-03-r) 0 0;
overflow: hidden;
background: linear-gradient(150deg,oklch(0.4 0.03 80),oklch(0.3 0.02 60));
}
.rib-03__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .7s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover .rib-03__img {
scale: 1.045;
}
.rib-03__body {
display: grid;
gap: 8px;
padding: clamp(18px,4vw,24px);
}
.rib-03__rank {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--rib-03-acc);
}
.rib-03__name {
font-size: 22px;
font-weight: 640;
letter-spacing: -.025em;
text-wrap: balance;
}
.rib-03__stars {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: var(--rib-03-mut);
}
.rib-03__starrow {
color: var(--rib-03-acc);
letter-spacing: .08em;
font-size: 14px;
}
.rib-03__rate {
font-weight: 700;
color: var(--rib-03-ink);
font-variant-numeric: tabular-nums;
}
.rib-03__price {
margin-top: 4px;
font-size: 26px;
font-weight: 750;
letter-spacing: -.03em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 10px;
}
.rib-03__unit {
font-size: 13px;
font-weight: 400;
color: var(--rib-03-mut);
}
.rib-03__cta {
margin-top: 8px;
min-height: 48px;
border: 0;
border-radius: 13px;
background: var(--rib-03-acc);
color: oklch(0.24 0.03 78);
font: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: filter .25s,translate .2s;
}
.rib-03__cta:hover {
filter: brightness(1.08);
translate: 0 -1px;
}
.rib-03__cta:focus-visible {
outline: 3px solid var(--rib-03-ink);
outline-offset: 3px;
}
.rib-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--rib-03-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.14);
border-radius: 99px;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.rib-03 * {
animation: none !important;
transition-duration: .01ms !important;
}
}.rib-03,
.rib-03 *,
.rib-03 *::before,
.rib-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rib-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--rib-03-bg: #14120f;
--rib-03-bg: oklch(0.19 0.014 75);
--rib-03-card: #221f1a;
--rib-03-card: oklch(0.26 0.016 78);
--rib-03-ink: #f7f3ec;
--rib-03-ink: oklch(0.97 0.012 85);
--rib-03-mut: #a8a096;
--rib-03-mut: oklch(0.72 0.018 82);
--rib-03-acc: #e2b357;
--rib-03-acc: oklch(0.81 0.14 82);
--rib-03-cut: 118px;
--rib-03-band: 38px;
--rib-03-over: 17px;
--rib-03-fold: 14px;
--rib-03-top: 30px;
--rib-03-len: calc(var(--rib-03-cut) * 1.415 + var(--rib-03-over) * 2);
--rib-03-r: 22px;
background: var(--rib-03-bg);
color: var(--rib-03-ink);
font-family: system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
-webkit-font-smoothing: antialiased;
}
.rib-03__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(18px,3vw,28px);
padding: clamp(24px,6vw,64px);
background: radial-gradient(90% 70% at 50% 8%,color-mix(in oklch,var(--rib-03-acc) 13%,transparent),transparent 66%);
}
.rib-03__card {
position: relative;
width: min(100%,376px);
overflow: visible;
border-radius: var(--rib-03-r);
background: var(--rib-03-card);
border: 1px solid oklch(1 0 0/.09);
box-shadow: 0 30px 70px -28px oklch(0 0 0/.85);
transition: translate .45s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover {
translate: 0 -5px;
}
.rib-03__card:hover .rib-03__ribbon {
rotate: -47deg;
translate: -2px -2px;
}
.rib-03__ribbon {
position: absolute;
z-index: 3;
inset-block-start: var(--rib-03-top);
inset-inline-start: calc((var(--rib-03-len) - var(--rib-03-cut)) / -2);
inline-size: var(--rib-03-len);
min-block-size: var(--rib-03-band);
display: grid;
place-items: center;
rotate: -45deg;
transition: rotate .5s cubic-bezier(.16,1,.3,1),translate .5s cubic-bezier(.16,1,.3,1);
background: #e2b357;
background: linear-gradient(100deg,color-mix(in oklch,var(--rib-03-acc) 72%,black 6%) 0%,color-mix(in oklch,var(--rib-03-acc) 100%,white 22%) 34%,var(--rib-03-acc) 52%,color-mix(in oklch,var(--rib-03-acc) 74%,black 8%) 100%);
background-size: 220% 100%;
box-shadow: 0 10px 24px -10px oklch(0 0 0/.7),inset 0 1px 0 oklch(1 0 0/.45),inset 0 -1px 0 oklch(0 0 0/.22);
animation: rib-03-foil 7s ease-in-out infinite;
}
@keyframes rib-03-foil {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.rib-03__label {
padding: 0 16px;
font-size: 12px;
font-weight: 800;
letter-spacing: .18em;
line-height: 1;
color: oklch(0.24 0.03 78);
text-shadow: 0 1px 0 oklch(1 0 0/.32);
}
.rib-03__ribbon::before,
.rib-03__ribbon::after {
content: '';
position: absolute;
inset-block-start: 100%;
inline-size: var(--rib-03-over);
block-size: var(--rib-03-fold);
background: #8a6a25;
background: color-mix(in oklch,var(--rib-03-acc) 58%,black);
z-index: -1;
}
.rib-03__ribbon::before {
inset-inline-start: 0;
clip-path: polygon(0 0,100% 0,100% 100%);
}
.rib-03__ribbon::after {
inset-inline-end: 0;
clip-path: polygon(0 0,100% 0,0 100%);
}
.rib-03__media {
aspect-ratio: 5/4;
border-radius: var(--rib-03-r) var(--rib-03-r) 0 0;
overflow: hidden;
background: linear-gradient(150deg,oklch(0.4 0.03 80),oklch(0.3 0.02 60));
}
.rib-03__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .7s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover .rib-03__img {
scale: 1.045;
}
.rib-03__body {
display: grid;
gap: 8px;
padding: clamp(18px,4vw,24px);
}
.rib-03__rank {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--rib-03-acc);
}
.rib-03__name {
font-size: 22px;
font-weight: 640;
letter-spacing: -.025em;
text-wrap: balance;
}
.rib-03__stars {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: var(--rib-03-mut);
}
.rib-03__starrow {
color: var(--rib-03-acc);
letter-spacing: .08em;
font-size: 14px;
}
.rib-03__rate {
font-weight: 700;
color: var(--rib-03-ink);
font-variant-numeric: tabular-nums;
}
.rib-03__price {
margin-top: 4px;
font-size: 26px;
font-weight: 750;
letter-spacing: -.03em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 10px;
}
.rib-03__unit {
font-size: 13px;
font-weight: 400;
color: var(--rib-03-mut);
}
.rib-03__cta {
margin-top: 8px;
min-height: 48px;
border: 0;
border-radius: 13px;
background: var(--rib-03-acc);
color: oklch(0.24 0.03 78);
font: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: filter .25s,translate .2s;
}
.rib-03__cta:hover {
filter: brightness(1.08);
translate: 0 -1px;
}
.rib-03__cta:focus-visible {
outline: 3px solid var(--rib-03-ink);
outline-offset: 3px;
}
.rib-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--rib-03-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.14);
border-radius: 99px;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.rib-03 * {
animation: none !important;
transition-duration: .01ms !important;
}
}Here's a working CSS Ribbon from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bestseller Corner Ribbon
Source: https://codefronts.com/snippets/css-ribbons/bestseller-corner-ribbon/
A corner ribbon that is tied on rather than printed on. Demo 01 clips its band flush to the card; this one crosses the top-left corner at 45° and keeps going, overhanging both card edges, with two darker triangles folding under the card where a real ribbon would disappear. Overhang plus fold is what makes it read as a physical band. Gold-foil gradient, slow animated shine, and a tilt that deepens on hover — all from two pseudo-elements and one rotate.
## HTML
```html
<section class="rib-03" aria-label="Featured product bestseller ribbon demo">
<div class="rib-03__stage">
<article class="rib-03__card">
<div class="rib-03__ribbon" aria-hidden="true"><span class="rib-03__label">BESTSELLER</span></div>
<div class="rib-03__media">
<img class="rib-03__img" alt="Black and silver over-ear studio headphones on a near-black backdrop" loading="lazy" decoding="async" width="640" height="520"
src="https://images.unsplash.com/photo-1487215078519-e21cc028cb29?q=80&w=800&auto=format&fit=crop">
</div>
<div class="rib-03__body">
<p class="rib-03__rank">Bestseller · #1 in Studio Headphones</p>
<h3 class="rib-03__name">Auralis One Reference</h3>
<p class="rib-03__stars"><span class="rib-03__starrow" aria-hidden="true">★★★★★</span><span class="rib-03__rate">4.9</span><span class="rib-03__count">2,418 reviews</span></p>
<p class="rib-03__price">$249<span class="rib-03__unit">or $21/mo</span></p>
<button class="rib-03__cta" type="button">View product</button>
</div>
</article>
<p class="rib-03__chip" aria-hidden="true">corner wrap · rotate:-45deg · S×√2 + overhang · clip-path fold tails · foil sweep</p>
</div>
</section>
```
## CSS
```css
.rib-03,
.rib-03 *,
.rib-03 *::before,
.rib-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rib-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--rib-03-bg: #14120f;
--rib-03-bg: oklch(0.19 0.014 75);
--rib-03-card: #221f1a;
--rib-03-card: oklch(0.26 0.016 78);
--rib-03-ink: #f7f3ec;
--rib-03-ink: oklch(0.97 0.012 85);
--rib-03-mut: #a8a096;
--rib-03-mut: oklch(0.72 0.018 82);
--rib-03-acc: #e2b357;
--rib-03-acc: oklch(0.81 0.14 82);
--rib-03-cut: 118px;
--rib-03-band: 38px;
--rib-03-over: 17px;
--rib-03-fold: 14px;
--rib-03-top: 30px;
--rib-03-len: calc(var(--rib-03-cut) * 1.415 + var(--rib-03-over) * 2);
--rib-03-r: 22px;
background: var(--rib-03-bg);
color: var(--rib-03-ink);
font-family: system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
-webkit-font-smoothing: antialiased;
}
.rib-03__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(18px,3vw,28px);
padding: clamp(24px,6vw,64px);
background: radial-gradient(90% 70% at 50% 8%,color-mix(in oklch,var(--rib-03-acc) 13%,transparent),transparent 66%);
}
.rib-03__card {
position: relative;
width: min(100%,376px);
overflow: visible;
border-radius: var(--rib-03-r);
background: var(--rib-03-card);
border: 1px solid oklch(1 0 0/.09);
box-shadow: 0 30px 70px -28px oklch(0 0 0/.85);
transition: translate .45s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover {
translate: 0 -5px;
}
.rib-03__card:hover .rib-03__ribbon {
rotate: -47deg;
translate: -2px -2px;
}
.rib-03__ribbon {
position: absolute;
z-index: 3;
inset-block-start: var(--rib-03-top);
inset-inline-start: calc((var(--rib-03-len) - var(--rib-03-cut)) / -2);
inline-size: var(--rib-03-len);
min-block-size: var(--rib-03-band);
display: grid;
place-items: center;
rotate: -45deg;
transition: rotate .5s cubic-bezier(.16,1,.3,1),translate .5s cubic-bezier(.16,1,.3,1);
background: #e2b357;
background: linear-gradient(100deg,color-mix(in oklch,var(--rib-03-acc) 72%,black 6%) 0%,color-mix(in oklch,var(--rib-03-acc) 100%,white 22%) 34%,var(--rib-03-acc) 52%,color-mix(in oklch,var(--rib-03-acc) 74%,black 8%) 100%);
background-size: 220% 100%;
box-shadow: 0 10px 24px -10px oklch(0 0 0/.7),inset 0 1px 0 oklch(1 0 0/.45),inset 0 -1px 0 oklch(0 0 0/.22);
animation: rib-03-foil 7s ease-in-out infinite;
}
@keyframes rib-03-foil {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.rib-03__label {
padding: 0 16px;
font-size: 12px;
font-weight: 800;
letter-spacing: .18em;
line-height: 1;
color: oklch(0.24 0.03 78);
text-shadow: 0 1px 0 oklch(1 0 0/.32);
}
.rib-03__ribbon::before,
.rib-03__ribbon::after {
content: '';
position: absolute;
inset-block-start: 100%;
inline-size: var(--rib-03-over);
block-size: var(--rib-03-fold);
background: #8a6a25;
background: color-mix(in oklch,var(--rib-03-acc) 58%,black);
z-index: -1;
}
.rib-03__ribbon::before {
inset-inline-start: 0;
clip-path: polygon(0 0,100% 0,100% 100%);
}
.rib-03__ribbon::after {
inset-inline-end: 0;
clip-path: polygon(0 0,100% 0,0 100%);
}
.rib-03__media {
aspect-ratio: 5/4;
border-radius: var(--rib-03-r) var(--rib-03-r) 0 0;
overflow: hidden;
background: linear-gradient(150deg,oklch(0.4 0.03 80),oklch(0.3 0.02 60));
}
.rib-03__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .7s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover .rib-03__img {
scale: 1.045;
}
.rib-03__body {
display: grid;
gap: 8px;
padding: clamp(18px,4vw,24px);
}
.rib-03__rank {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--rib-03-acc);
}
.rib-03__name {
font-size: 22px;
font-weight: 640;
letter-spacing: -.025em;
text-wrap: balance;
}
.rib-03__stars {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: var(--rib-03-mut);
}
.rib-03__starrow {
color: var(--rib-03-acc);
letter-spacing: .08em;
font-size: 14px;
}
.rib-03__rate {
font-weight: 700;
color: var(--rib-03-ink);
font-variant-numeric: tabular-nums;
}
.rib-03__price {
margin-top: 4px;
font-size: 26px;
font-weight: 750;
letter-spacing: -.03em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 10px;
}
.rib-03__unit {
font-size: 13px;
font-weight: 400;
color: var(--rib-03-mut);
}
.rib-03__cta {
margin-top: 8px;
min-height: 48px;
border: 0;
border-radius: 13px;
background: var(--rib-03-acc);
color: oklch(0.24 0.03 78);
font: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: filter .25s,translate .2s;
}
.rib-03__cta:hover {
filter: brightness(1.08);
translate: 0 -1px;
}
.rib-03__cta:focus-visible {
outline: 3px solid var(--rib-03-ink);
outline-offset: 3px;
}
.rib-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--rib-03-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.14);
border-radius: 99px;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.rib-03 * {
animation: none !important;
transition-duration: .01ms !important;
}
}
```Here's a working CSS Ribbon from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bestseller Corner Ribbon
Source: https://codefronts.com/snippets/css-ribbons/bestseller-corner-ribbon/
A corner ribbon that is tied on rather than printed on. Demo 01 clips its band flush to the card; this one crosses the top-left corner at 45° and keeps going, overhanging both card edges, with two darker triangles folding under the card where a real ribbon would disappear. Overhang plus fold is what makes it read as a physical band. Gold-foil gradient, slow animated shine, and a tilt that deepens on hover — all from two pseudo-elements and one rotate.
## HTML
```html
<section class="rib-03" aria-label="Featured product bestseller ribbon demo">
<div class="rib-03__stage">
<article class="rib-03__card">
<div class="rib-03__ribbon" aria-hidden="true"><span class="rib-03__label">BESTSELLER</span></div>
<div class="rib-03__media">
<img class="rib-03__img" alt="Black and silver over-ear studio headphones on a near-black backdrop" loading="lazy" decoding="async" width="640" height="520"
src="https://images.unsplash.com/photo-1487215078519-e21cc028cb29?q=80&w=800&auto=format&fit=crop">
</div>
<div class="rib-03__body">
<p class="rib-03__rank">Bestseller · #1 in Studio Headphones</p>
<h3 class="rib-03__name">Auralis One Reference</h3>
<p class="rib-03__stars"><span class="rib-03__starrow" aria-hidden="true">★★★★★</span><span class="rib-03__rate">4.9</span><span class="rib-03__count">2,418 reviews</span></p>
<p class="rib-03__price">$249<span class="rib-03__unit">or $21/mo</span></p>
<button class="rib-03__cta" type="button">View product</button>
</div>
</article>
<p class="rib-03__chip" aria-hidden="true">corner wrap · rotate:-45deg · S×√2 + overhang · clip-path fold tails · foil sweep</p>
</div>
</section>
```
## CSS
```css
.rib-03,
.rib-03 *,
.rib-03 *::before,
.rib-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.rib-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--rib-03-bg: #14120f;
--rib-03-bg: oklch(0.19 0.014 75);
--rib-03-card: #221f1a;
--rib-03-card: oklch(0.26 0.016 78);
--rib-03-ink: #f7f3ec;
--rib-03-ink: oklch(0.97 0.012 85);
--rib-03-mut: #a8a096;
--rib-03-mut: oklch(0.72 0.018 82);
--rib-03-acc: #e2b357;
--rib-03-acc: oklch(0.81 0.14 82);
--rib-03-cut: 118px;
--rib-03-band: 38px;
--rib-03-over: 17px;
--rib-03-fold: 14px;
--rib-03-top: 30px;
--rib-03-len: calc(var(--rib-03-cut) * 1.415 + var(--rib-03-over) * 2);
--rib-03-r: 22px;
background: var(--rib-03-bg);
color: var(--rib-03-ink);
font-family: system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
-webkit-font-smoothing: antialiased;
}
.rib-03__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(18px,3vw,28px);
padding: clamp(24px,6vw,64px);
background: radial-gradient(90% 70% at 50% 8%,color-mix(in oklch,var(--rib-03-acc) 13%,transparent),transparent 66%);
}
.rib-03__card {
position: relative;
width: min(100%,376px);
overflow: visible;
border-radius: var(--rib-03-r);
background: var(--rib-03-card);
border: 1px solid oklch(1 0 0/.09);
box-shadow: 0 30px 70px -28px oklch(0 0 0/.85);
transition: translate .45s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover {
translate: 0 -5px;
}
.rib-03__card:hover .rib-03__ribbon {
rotate: -47deg;
translate: -2px -2px;
}
.rib-03__ribbon {
position: absolute;
z-index: 3;
inset-block-start: var(--rib-03-top);
inset-inline-start: calc((var(--rib-03-len) - var(--rib-03-cut)) / -2);
inline-size: var(--rib-03-len);
min-block-size: var(--rib-03-band);
display: grid;
place-items: center;
rotate: -45deg;
transition: rotate .5s cubic-bezier(.16,1,.3,1),translate .5s cubic-bezier(.16,1,.3,1);
background: #e2b357;
background: linear-gradient(100deg,color-mix(in oklch,var(--rib-03-acc) 72%,black 6%) 0%,color-mix(in oklch,var(--rib-03-acc) 100%,white 22%) 34%,var(--rib-03-acc) 52%,color-mix(in oklch,var(--rib-03-acc) 74%,black 8%) 100%);
background-size: 220% 100%;
box-shadow: 0 10px 24px -10px oklch(0 0 0/.7),inset 0 1px 0 oklch(1 0 0/.45),inset 0 -1px 0 oklch(0 0 0/.22);
animation: rib-03-foil 7s ease-in-out infinite;
}
@keyframes rib-03-foil {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.rib-03__label {
padding: 0 16px;
font-size: 12px;
font-weight: 800;
letter-spacing: .18em;
line-height: 1;
color: oklch(0.24 0.03 78);
text-shadow: 0 1px 0 oklch(1 0 0/.32);
}
.rib-03__ribbon::before,
.rib-03__ribbon::after {
content: '';
position: absolute;
inset-block-start: 100%;
inline-size: var(--rib-03-over);
block-size: var(--rib-03-fold);
background: #8a6a25;
background: color-mix(in oklch,var(--rib-03-acc) 58%,black);
z-index: -1;
}
.rib-03__ribbon::before {
inset-inline-start: 0;
clip-path: polygon(0 0,100% 0,100% 100%);
}
.rib-03__ribbon::after {
inset-inline-end: 0;
clip-path: polygon(0 0,100% 0,0 100%);
}
.rib-03__media {
aspect-ratio: 5/4;
border-radius: var(--rib-03-r) var(--rib-03-r) 0 0;
overflow: hidden;
background: linear-gradient(150deg,oklch(0.4 0.03 80),oklch(0.3 0.02 60));
}
.rib-03__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .7s cubic-bezier(.16,1,.3,1);
}
.rib-03__card:hover .rib-03__img {
scale: 1.045;
}
.rib-03__body {
display: grid;
gap: 8px;
padding: clamp(18px,4vw,24px);
}
.rib-03__rank {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--rib-03-acc);
}
.rib-03__name {
font-size: 22px;
font-weight: 640;
letter-spacing: -.025em;
text-wrap: balance;
}
.rib-03__stars {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: var(--rib-03-mut);
}
.rib-03__starrow {
color: var(--rib-03-acc);
letter-spacing: .08em;
font-size: 14px;
}
.rib-03__rate {
font-weight: 700;
color: var(--rib-03-ink);
font-variant-numeric: tabular-nums;
}
.rib-03__price {
margin-top: 4px;
font-size: 26px;
font-weight: 750;
letter-spacing: -.03em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 10px;
}
.rib-03__unit {
font-size: 13px;
font-weight: 400;
color: var(--rib-03-mut);
}
.rib-03__cta {
margin-top: 8px;
min-height: 48px;
border: 0;
border-radius: 13px;
background: var(--rib-03-acc);
color: oklch(0.24 0.03 78);
font: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: filter .25s,translate .2s;
}
.rib-03__cta:hover {
filter: brightness(1.08);
translate: 0 -1px;
}
.rib-03__cta:focus-visible {
outline: 3px solid var(--rib-03-ink);
outline-offset: 3px;
}
.rib-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
color: var(--rib-03-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.14);
border-radius: 99px;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.rib-03 * {
animation: none !important;
transition-duration: .01ms !important;
}
}
```How this works
A wrapped corner ribbon needs three parts: the visible band, and two folds tucked under the card. The band crosses a corner box of side --cut, so its length starts from that diagonal, cut × √2, and then adds an overhang at each end. Nothing may clip it:
.card { overflow: visible; } /* let the ribbon stick out */
.ribbon {
--len: calc(var(--cut) * 1.415 + var(--over) * 2); /* diagonal + overhang */
position: absolute;
inset-block-start: var(--top); /* slide along the diagonal */
inset-inline-start: calc((var(--len) - var(--cut)) / -2); /* centre on the corner */
inline-size: var(--len);
rotate: -45deg; /* mirror of demo 01's +45° */
}Rotation happens about the band's own centre, so the two ends swing out symmetrically: one crosses the card's top edge, the other its left edge, each clearing it by over × 0.707. That factor is cos 45° — the overhang you type is measured along the band, and only its horizontal share pokes past the edge.
The folds are pure triangles made with clip-path on the pseudo-elements, painted a darker shade of the same hue via color-mix() so they read as the shadowed underside of one continuous ribbon rather than two separate shapes:
.ribbon::before, .ribbon::after {
content: ''; position: absolute; inset-block-start: 100%;
inline-size: var(--over); block-size: var(--fold);
background: color-mix(in oklch, var(--acc) 62%, black);
z-index: -1;
}
.ribbon::before { inset-inline-start: 0; clip-path: polygon(0 0, 100% 0, 100% 100%); }
.ribbon::after { inset-inline-end: 0; clip-path: polygon(0 0, 100% 0, 0 100%); }Each triangle hangs from the bottom edge of the band (inset-block-start: 100%) at an overhanging end, pointing inward — exactly where real ribbon would disappear behind the card. Because the folds are children of the rotated band they inherit the 45°, and because they sit entirely in the overhang they never overlap the card, so the illusion needs no stacking-order gymnastics. z-index: -1 only hides the seam behind the band.
The foil is a three-stop linear-gradient plus a slow background-position sweep, which costs one animated property and no extra elements. The whole thing is aria-hidden — "BESTSELLER" is repeated as real text in the card's rank line, because a decorative ribbon should never be the only place information lives.
Make it yours
- Tune the personality with two numbers:
--rib-03-cut(how much corner the band eats) and--rib-03-over(how far it sticks out). A short cut with a long overhang reads like a gift ribbon; a long cut with almost no overhang reads like demo 01’s flush corner badge. - Mirror it to the top-right by swapping
inset-inline-startforinset-inline-endandrotate: -45degfor45deg— then swap the two fold triangles’clip-pathvalues so the tails still point inward. - Vertical wrap: rotate the whole ribbon 90° with
writing-mode: vertical-rlon the label and swap the fold triangles to the top and bottom edges for a spine-wrap effect. - Change foil to matte by replacing the gradient with a flat
var(--rib-03-acc)and deleting the shine keyframes — the geometry is untouched. - Make it responsive to card width with a container query:
@container (width < 300px) { .ribbon { --over: 10px; font-size: 11px } }so the overhang never dominates a small card.
Gotchas — read before shipping
- Overhang and rotation must be budgeted together. Increase
--rib-03-overwithout checking and the far end swings past the card corner entirely, leaving the band floating; increase--rib-03-cutpast roughly half the card width and the near end walks off the bottom of the media. Change one, eyeball both ends. overflow: hiddenanywhere up the tree kills the overhang. That includes carousels and masonry wrappers — a wrapped ribbon and a clipping ancestor are mutually exclusive.z-index: -1on the folds only works if the card creates a stacking context and has its own background. Without it, the triangles slide behind the page background and vanish.- Overhanging elements can trigger horizontal page scroll on mobile. Give the grid container
overflow-x: clip(not hidden — clip does not create a scroll container) or add padding equal to the overhang. - Don't put the only copy of the word BESTSELLER inside an aria-hidden ribbon. Screen-reader and search-engine value both come from the real text node.
- Gold on white fails contrast fast. This demo uses a dark ink label on the foil rather than white text — check any foil ribbon at 4.5:1 before shipping.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 88+.
clip-path polygon on pseudo-elements is universally supported; color-mix() needs Chrome 111 / Safari 16.2 / Firefox 113 and falls back to the plain hex declared immediately before each color-mix() value. Container queries (optional here) are Baseline from Firefox 110.