24 CSS Animated Cards23 / 24
Corner Ribbon Slide Animation
Corner ribbons that arrive instead of just existing: a diagonal banner sliding out of the card corner on load, folded end-tabs cut with clip-path, and a hover wiggle with a light sweep running along the fabric. Pure CSS, no images, and the ribbon text stays readable to screen readers rather than being baked into a background.
Published
The code
<section class="ac-23" aria-label="Corner ribbon slide animation demo">
<div class="ac-23__stage">
<header class="ac-23__head">
<p class="ac-23__eyebrow">Merchandising</p>
<h2 class="ac-23__title">Ribbons that unfurl from the corner</h2>
</header>
<ul class="ac-23__grid" role="list">
<li class="ac-23__card" style="--ac-23-i:0;--ac-23-c:oklch(0.62 0.21 25)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Sale</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=600&auto=format&fit=crop" alt="Over-ear headphones on a plain background" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Studio Over-Ear</h3>
<p class="ac-23__price"><b>$199</b><s>$249</s></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:1;--ac-23-c:oklch(0.68 0.18 152)">
<span class="ac-23__corner"><span class="ac-23__ribbon">New</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1546868871-7041f2a55e12?q=80&w=600&auto=format&fit=crop" alt="Smartwatch with a woven band" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Pace Watch S2</h3>
<p class="ac-23__price"><b>$219</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:2;--ac-23-c:oklch(0.78 0.16 82)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Limited</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=600&auto=format&fit=crop" alt="Analogue wristwatch on a light surface" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Field Watch 38</h3>
<p class="ac-23__price"><b>$390</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:3;--ac-23-c:oklch(0.58 0.19 285)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Bundle</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?q=80&w=600&auto=format&fit=crop" alt="Compact camera body on a table" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Rangefinder M</h3>
<p class="ac-23__price"><b>$1,180</b></p>
</div>
</li>
</ul>
<p class="ac-23__chip" aria-hidden="true">clipped corner wrapper · rotate(45deg) + diagonal translate unfurl · clip-path fold tabs · hover wiggle + sheen · real text</p>
</div>
</section><section class="ac-23" aria-label="Corner ribbon slide animation demo">
<div class="ac-23__stage">
<header class="ac-23__head">
<p class="ac-23__eyebrow">Merchandising</p>
<h2 class="ac-23__title">Ribbons that unfurl from the corner</h2>
</header>
<ul class="ac-23__grid" role="list">
<li class="ac-23__card" style="--ac-23-i:0;--ac-23-c:oklch(0.62 0.21 25)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Sale</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=600&auto=format&fit=crop" alt="Over-ear headphones on a plain background" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Studio Over-Ear</h3>
<p class="ac-23__price"><b>$199</b><s>$249</s></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:1;--ac-23-c:oklch(0.68 0.18 152)">
<span class="ac-23__corner"><span class="ac-23__ribbon">New</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1546868871-7041f2a55e12?q=80&w=600&auto=format&fit=crop" alt="Smartwatch with a woven band" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Pace Watch S2</h3>
<p class="ac-23__price"><b>$219</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:2;--ac-23-c:oklch(0.78 0.16 82)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Limited</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=600&auto=format&fit=crop" alt="Analogue wristwatch on a light surface" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Field Watch 38</h3>
<p class="ac-23__price"><b>$390</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:3;--ac-23-c:oklch(0.58 0.19 285)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Bundle</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?q=80&w=600&auto=format&fit=crop" alt="Compact camera body on a table" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Rangefinder M</h3>
<p class="ac-23__price"><b>$1,180</b></p>
</div>
</li>
</ul>
<p class="ac-23__chip" aria-hidden="true">clipped corner wrapper · rotate(45deg) + diagonal translate unfurl · clip-path fold tabs · hover wiggle + sheen · real text</p>
</div>
</section>.ac-23,
.ac-23 *,
.ac-23 *::before,
.ac-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-23 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-23-bg: oklch(0.97 0.006 250);
--ac-23-card: oklch(1 0 0);
--ac-23-ink: oklch(0.23 0.02 260);
--ac-23-mut: oklch(0.55 0.02 260);
--ac-23-line: oklch(0.23 0.02 260/.11);
background: var(--ac-23-bg);
color: var(--ac-23-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-23__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(16px,2.6vw,28px);
padding: clamp(20px,5vw,58px);
}
.ac-23__head {
display: grid;
justify-items: center;
gap: 9px;
text-align: center;
}
.ac-23__eyebrow {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: oklch(0.55 0.16 25);
}
.ac-23__title {
font-size: clamp(23px,3.4vw,38px);
font-weight: 650;
line-height: 1.12;
letter-spacing: -.03em;
text-wrap: balance;
}
.ac-23__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(196px,1fr));
gap: 16px;
width: min(100%,900px);
list-style: none;
}
.ac-23__card {
position: relative;
display: grid;
border: 1px solid var(--ac-23-line);
border-radius: 18px;
background: var(--ac-23-card);
box-shadow: 0 1px 2px oklch(0.23 0.02 260/.05);
overflow: hidden;
transition: transform .35s cubic-bezier(.16,1,.3,1),box-shadow .35s;
}
.ac-23__card:hover {
transform: translateY(-4px);
box-shadow: 0 22px 44px -26px oklch(0.23 0.02 260/.6);
}
.ac-23__corner {
position: absolute;
top: 0;
right: 0;
width: 124px;
height: 124px;
overflow: hidden;
border-radius: 0 18px 0 0;
z-index: 2;
pointer-events: none;
}
.ac-23__ribbon {
position: absolute;
top: 26px;
right: -44px;
width: 186px;
padding: 7px 0;
rotate: 45deg;
text-align: center;
background: var(--ac-23-c);
color: oklch(0.99 0 0);
font-size: 11.5px;
font-weight: 750;
letter-spacing: .15em;
text-transform: uppercase;
box-shadow: 0 6px 16px -8px color-mix(in oklch,var(--ac-23-c) 90%,oklch(0 0 0));
animation: ac-23-unfurl .72s cubic-bezier(.16,1,.3,1) both;
animation-delay: calc(.3s + var(--ac-23-i,0) * 110ms);
}
@keyframes ac-23-unfurl {
from {
translate: 62px -62px;
opacity: 0;
}
60% {
opacity: 1;
}
to {
translate: 0 0;
opacity: 1;
}
}
.ac-23__ribbon::before,
.ac-23__ribbon::after {
content: '';
position: absolute;
top: 100%;
width: 0;
height: 0;
border-style: solid;
border-color: color-mix(in oklch,var(--ac-23-c) 70%,oklch(0 0 0)) transparent transparent;
}
.ac-23__ribbon::before {
left: 34px;
border-width: 7px 7px 0 0;
}
.ac-23__ribbon::after {
right: 34px;
border-width: 7px 0 0 7px;
}
.ac-23__card:hover .ac-23__ribbon {
animation: ac-23-wiggle .55s cubic-bezier(.3,0,.3,1);
}
@keyframes ac-23-wiggle {
0%,
100% {
rotate: 45deg;
}
30% {
rotate: 43.6deg;
}
65% {
rotate: 46.2deg;
}
}
.ac-23__card:hover .ac-23__corner::after {
content: '';
position: absolute;
top: 26px;
right: -44px;
width: 186px;
height: 31px;
rotate: 45deg;
background: linear-gradient(105deg,transparent 36%,oklch(1 0 0/.5) 50%,transparent 64%);
animation: ac-23-sheen .8s ease-out;
pointer-events: none;
}
@keyframes ac-23-sheen {
from {
translate: -120px 0;
opacity: 0;
}
30% {
opacity: 1;
}
to {
translate: 120px 0;
opacity: 0;
}
}
.ac-23__media {
display: block;
aspect-ratio: 1;
background: linear-gradient(135deg,oklch(0.92 0.02 250),oklch(0.86 0.03 220));
}
.ac-23__media img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .5s cubic-bezier(.16,1,.3,1);
}
.ac-23__card:hover .ac-23__media img {
scale: 1.06;
}
.ac-23__body {
display: grid;
gap: 5px;
padding: 14px 15px 17px;
}
.ac-23__name {
font-size: 14.5px;
font-weight: 640;
letter-spacing: -.02em;
}
.ac-23__price {
display: flex;
align-items: baseline;
gap: 8px;
font-variant-numeric: tabular-nums;
}
.ac-23__price b {
font-size: 16px;
font-weight: 700;
letter-spacing: -.025em;
}
.ac-23__price s {
font-size: 13px;
color: var(--ac-23-mut);
}
.ac-23__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-23-mut);
padding: 8px 15px;
border: 1px dashed var(--ac-23-line);
border-radius: 99px;
text-align: center;
max-width: min(100%,820px);
}
@media (prefers-reduced-motion: reduce) {
.ac-23__ribbon {
animation-duration: 1ms;
}
.ac-23__card:hover .ac-23__ribbon {
animation: none;
}
.ac-23__card:hover .ac-23__corner::after {
display: none;
}
.ac-23__card,
.ac-23__media img {
transition: none;
}
}.ac-23,
.ac-23 *,
.ac-23 *::before,
.ac-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-23 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-23-bg: oklch(0.97 0.006 250);
--ac-23-card: oklch(1 0 0);
--ac-23-ink: oklch(0.23 0.02 260);
--ac-23-mut: oklch(0.55 0.02 260);
--ac-23-line: oklch(0.23 0.02 260/.11);
background: var(--ac-23-bg);
color: var(--ac-23-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-23__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(16px,2.6vw,28px);
padding: clamp(20px,5vw,58px);
}
.ac-23__head {
display: grid;
justify-items: center;
gap: 9px;
text-align: center;
}
.ac-23__eyebrow {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: oklch(0.55 0.16 25);
}
.ac-23__title {
font-size: clamp(23px,3.4vw,38px);
font-weight: 650;
line-height: 1.12;
letter-spacing: -.03em;
text-wrap: balance;
}
.ac-23__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(196px,1fr));
gap: 16px;
width: min(100%,900px);
list-style: none;
}
.ac-23__card {
position: relative;
display: grid;
border: 1px solid var(--ac-23-line);
border-radius: 18px;
background: var(--ac-23-card);
box-shadow: 0 1px 2px oklch(0.23 0.02 260/.05);
overflow: hidden;
transition: transform .35s cubic-bezier(.16,1,.3,1),box-shadow .35s;
}
.ac-23__card:hover {
transform: translateY(-4px);
box-shadow: 0 22px 44px -26px oklch(0.23 0.02 260/.6);
}
.ac-23__corner {
position: absolute;
top: 0;
right: 0;
width: 124px;
height: 124px;
overflow: hidden;
border-radius: 0 18px 0 0;
z-index: 2;
pointer-events: none;
}
.ac-23__ribbon {
position: absolute;
top: 26px;
right: -44px;
width: 186px;
padding: 7px 0;
rotate: 45deg;
text-align: center;
background: var(--ac-23-c);
color: oklch(0.99 0 0);
font-size: 11.5px;
font-weight: 750;
letter-spacing: .15em;
text-transform: uppercase;
box-shadow: 0 6px 16px -8px color-mix(in oklch,var(--ac-23-c) 90%,oklch(0 0 0));
animation: ac-23-unfurl .72s cubic-bezier(.16,1,.3,1) both;
animation-delay: calc(.3s + var(--ac-23-i,0) * 110ms);
}
@keyframes ac-23-unfurl {
from {
translate: 62px -62px;
opacity: 0;
}
60% {
opacity: 1;
}
to {
translate: 0 0;
opacity: 1;
}
}
.ac-23__ribbon::before,
.ac-23__ribbon::after {
content: '';
position: absolute;
top: 100%;
width: 0;
height: 0;
border-style: solid;
border-color: color-mix(in oklch,var(--ac-23-c) 70%,oklch(0 0 0)) transparent transparent;
}
.ac-23__ribbon::before {
left: 34px;
border-width: 7px 7px 0 0;
}
.ac-23__ribbon::after {
right: 34px;
border-width: 7px 0 0 7px;
}
.ac-23__card:hover .ac-23__ribbon {
animation: ac-23-wiggle .55s cubic-bezier(.3,0,.3,1);
}
@keyframes ac-23-wiggle {
0%,
100% {
rotate: 45deg;
}
30% {
rotate: 43.6deg;
}
65% {
rotate: 46.2deg;
}
}
.ac-23__card:hover .ac-23__corner::after {
content: '';
position: absolute;
top: 26px;
right: -44px;
width: 186px;
height: 31px;
rotate: 45deg;
background: linear-gradient(105deg,transparent 36%,oklch(1 0 0/.5) 50%,transparent 64%);
animation: ac-23-sheen .8s ease-out;
pointer-events: none;
}
@keyframes ac-23-sheen {
from {
translate: -120px 0;
opacity: 0;
}
30% {
opacity: 1;
}
to {
translate: 120px 0;
opacity: 0;
}
}
.ac-23__media {
display: block;
aspect-ratio: 1;
background: linear-gradient(135deg,oklch(0.92 0.02 250),oklch(0.86 0.03 220));
}
.ac-23__media img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .5s cubic-bezier(.16,1,.3,1);
}
.ac-23__card:hover .ac-23__media img {
scale: 1.06;
}
.ac-23__body {
display: grid;
gap: 5px;
padding: 14px 15px 17px;
}
.ac-23__name {
font-size: 14.5px;
font-weight: 640;
letter-spacing: -.02em;
}
.ac-23__price {
display: flex;
align-items: baseline;
gap: 8px;
font-variant-numeric: tabular-nums;
}
.ac-23__price b {
font-size: 16px;
font-weight: 700;
letter-spacing: -.025em;
}
.ac-23__price s {
font-size: 13px;
color: var(--ac-23-mut);
}
.ac-23__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-23-mut);
padding: 8px 15px;
border: 1px dashed var(--ac-23-line);
border-radius: 99px;
text-align: center;
max-width: min(100%,820px);
}
@media (prefers-reduced-motion: reduce) {
.ac-23__ribbon {
animation-duration: 1ms;
}
.ac-23__card:hover .ac-23__ribbon {
animation: none;
}
.ac-23__card:hover .ac-23__corner::after {
display: none;
}
.ac-23__card,
.ac-23__media img {
transition: none;
}
}Here's a working CSS Animated Card 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: Corner Ribbon Slide Animation
Source: https://codefronts.com/components/css-animated-cards/corner-ribbon-slide-animation/
Corner ribbons that arrive instead of just existing: a diagonal banner sliding out of the card corner on load, folded end-tabs cut with clip-path, and a hover wiggle with a light sweep running along the fabric. Pure CSS, no images, and the ribbon text stays readable to screen readers rather than being baked into a background.
## HTML
```html
<section class="ac-23" aria-label="Corner ribbon slide animation demo">
<div class="ac-23__stage">
<header class="ac-23__head">
<p class="ac-23__eyebrow">Merchandising</p>
<h2 class="ac-23__title">Ribbons that unfurl from the corner</h2>
</header>
<ul class="ac-23__grid" role="list">
<li class="ac-23__card" style="--ac-23-i:0;--ac-23-c:oklch(0.62 0.21 25)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Sale</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=600&auto=format&fit=crop" alt="Over-ear headphones on a plain background" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Studio Over-Ear</h3>
<p class="ac-23__price"><b>$199</b><s>$249</s></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:1;--ac-23-c:oklch(0.68 0.18 152)">
<span class="ac-23__corner"><span class="ac-23__ribbon">New</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1546868871-7041f2a55e12?q=80&w=600&auto=format&fit=crop" alt="Smartwatch with a woven band" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Pace Watch S2</h3>
<p class="ac-23__price"><b>$219</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:2;--ac-23-c:oklch(0.78 0.16 82)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Limited</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=600&auto=format&fit=crop" alt="Analogue wristwatch on a light surface" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Field Watch 38</h3>
<p class="ac-23__price"><b>$390</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:3;--ac-23-c:oklch(0.58 0.19 285)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Bundle</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?q=80&w=600&auto=format&fit=crop" alt="Compact camera body on a table" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Rangefinder M</h3>
<p class="ac-23__price"><b>$1,180</b></p>
</div>
</li>
</ul>
<p class="ac-23__chip" aria-hidden="true">clipped corner wrapper · rotate(45deg) + diagonal translate unfurl · clip-path fold tabs · hover wiggle + sheen · real text</p>
</div>
</section>
```
## CSS
```css
.ac-23,
.ac-23 *,
.ac-23 *::before,
.ac-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-23 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-23-bg: oklch(0.97 0.006 250);
--ac-23-card: oklch(1 0 0);
--ac-23-ink: oklch(0.23 0.02 260);
--ac-23-mut: oklch(0.55 0.02 260);
--ac-23-line: oklch(0.23 0.02 260/.11);
background: var(--ac-23-bg);
color: var(--ac-23-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-23__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(16px,2.6vw,28px);
padding: clamp(20px,5vw,58px);
}
.ac-23__head {
display: grid;
justify-items: center;
gap: 9px;
text-align: center;
}
.ac-23__eyebrow {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: oklch(0.55 0.16 25);
}
.ac-23__title {
font-size: clamp(23px,3.4vw,38px);
font-weight: 650;
line-height: 1.12;
letter-spacing: -.03em;
text-wrap: balance;
}
.ac-23__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(196px,1fr));
gap: 16px;
width: min(100%,900px);
list-style: none;
}
.ac-23__card {
position: relative;
display: grid;
border: 1px solid var(--ac-23-line);
border-radius: 18px;
background: var(--ac-23-card);
box-shadow: 0 1px 2px oklch(0.23 0.02 260/.05);
overflow: hidden;
transition: transform .35s cubic-bezier(.16,1,.3,1),box-shadow .35s;
}
.ac-23__card:hover {
transform: translateY(-4px);
box-shadow: 0 22px 44px -26px oklch(0.23 0.02 260/.6);
}
.ac-23__corner {
position: absolute;
top: 0;
right: 0;
width: 124px;
height: 124px;
overflow: hidden;
border-radius: 0 18px 0 0;
z-index: 2;
pointer-events: none;
}
.ac-23__ribbon {
position: absolute;
top: 26px;
right: -44px;
width: 186px;
padding: 7px 0;
rotate: 45deg;
text-align: center;
background: var(--ac-23-c);
color: oklch(0.99 0 0);
font-size: 11.5px;
font-weight: 750;
letter-spacing: .15em;
text-transform: uppercase;
box-shadow: 0 6px 16px -8px color-mix(in oklch,var(--ac-23-c) 90%,oklch(0 0 0));
animation: ac-23-unfurl .72s cubic-bezier(.16,1,.3,1) both;
animation-delay: calc(.3s + var(--ac-23-i,0) * 110ms);
}
@keyframes ac-23-unfurl {
from {
translate: 62px -62px;
opacity: 0;
}
60% {
opacity: 1;
}
to {
translate: 0 0;
opacity: 1;
}
}
.ac-23__ribbon::before,
.ac-23__ribbon::after {
content: '';
position: absolute;
top: 100%;
width: 0;
height: 0;
border-style: solid;
border-color: color-mix(in oklch,var(--ac-23-c) 70%,oklch(0 0 0)) transparent transparent;
}
.ac-23__ribbon::before {
left: 34px;
border-width: 7px 7px 0 0;
}
.ac-23__ribbon::after {
right: 34px;
border-width: 7px 0 0 7px;
}
.ac-23__card:hover .ac-23__ribbon {
animation: ac-23-wiggle .55s cubic-bezier(.3,0,.3,1);
}
@keyframes ac-23-wiggle {
0%,
100% {
rotate: 45deg;
}
30% {
rotate: 43.6deg;
}
65% {
rotate: 46.2deg;
}
}
.ac-23__card:hover .ac-23__corner::after {
content: '';
position: absolute;
top: 26px;
right: -44px;
width: 186px;
height: 31px;
rotate: 45deg;
background: linear-gradient(105deg,transparent 36%,oklch(1 0 0/.5) 50%,transparent 64%);
animation: ac-23-sheen .8s ease-out;
pointer-events: none;
}
@keyframes ac-23-sheen {
from {
translate: -120px 0;
opacity: 0;
}
30% {
opacity: 1;
}
to {
translate: 120px 0;
opacity: 0;
}
}
.ac-23__media {
display: block;
aspect-ratio: 1;
background: linear-gradient(135deg,oklch(0.92 0.02 250),oklch(0.86 0.03 220));
}
.ac-23__media img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .5s cubic-bezier(.16,1,.3,1);
}
.ac-23__card:hover .ac-23__media img {
scale: 1.06;
}
.ac-23__body {
display: grid;
gap: 5px;
padding: 14px 15px 17px;
}
.ac-23__name {
font-size: 14.5px;
font-weight: 640;
letter-spacing: -.02em;
}
.ac-23__price {
display: flex;
align-items: baseline;
gap: 8px;
font-variant-numeric: tabular-nums;
}
.ac-23__price b {
font-size: 16px;
font-weight: 700;
letter-spacing: -.025em;
}
.ac-23__price s {
font-size: 13px;
color: var(--ac-23-mut);
}
.ac-23__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-23-mut);
padding: 8px 15px;
border: 1px dashed var(--ac-23-line);
border-radius: 99px;
text-align: center;
max-width: min(100%,820px);
}
@media (prefers-reduced-motion: reduce) {
.ac-23__ribbon {
animation-duration: 1ms;
}
.ac-23__card:hover .ac-23__ribbon {
animation: none;
}
.ac-23__card:hover .ac-23__corner::after {
display: none;
}
.ac-23__card,
.ac-23__media img {
transition: none;
}
}
```Here's a working CSS Animated Card 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: Corner Ribbon Slide Animation
Source: https://codefronts.com/components/css-animated-cards/corner-ribbon-slide-animation/
Corner ribbons that arrive instead of just existing: a diagonal banner sliding out of the card corner on load, folded end-tabs cut with clip-path, and a hover wiggle with a light sweep running along the fabric. Pure CSS, no images, and the ribbon text stays readable to screen readers rather than being baked into a background.
## HTML
```html
<section class="ac-23" aria-label="Corner ribbon slide animation demo">
<div class="ac-23__stage">
<header class="ac-23__head">
<p class="ac-23__eyebrow">Merchandising</p>
<h2 class="ac-23__title">Ribbons that unfurl from the corner</h2>
</header>
<ul class="ac-23__grid" role="list">
<li class="ac-23__card" style="--ac-23-i:0;--ac-23-c:oklch(0.62 0.21 25)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Sale</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=600&auto=format&fit=crop" alt="Over-ear headphones on a plain background" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Studio Over-Ear</h3>
<p class="ac-23__price"><b>$199</b><s>$249</s></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:1;--ac-23-c:oklch(0.68 0.18 152)">
<span class="ac-23__corner"><span class="ac-23__ribbon">New</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1546868871-7041f2a55e12?q=80&w=600&auto=format&fit=crop" alt="Smartwatch with a woven band" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Pace Watch S2</h3>
<p class="ac-23__price"><b>$219</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:2;--ac-23-c:oklch(0.78 0.16 82)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Limited</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?q=80&w=600&auto=format&fit=crop" alt="Analogue wristwatch on a light surface" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Field Watch 38</h3>
<p class="ac-23__price"><b>$390</b></p>
</div>
</li>
<li class="ac-23__card" style="--ac-23-i:3;--ac-23-c:oklch(0.58 0.19 285)">
<span class="ac-23__corner"><span class="ac-23__ribbon">Bundle</span></span>
<span class="ac-23__media"><img src="https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?q=80&w=600&auto=format&fit=crop" alt="Compact camera body on a table" loading="lazy" decoding="async" width="600" height="600"></span>
<div class="ac-23__body">
<h3 class="ac-23__name">Rangefinder M</h3>
<p class="ac-23__price"><b>$1,180</b></p>
</div>
</li>
</ul>
<p class="ac-23__chip" aria-hidden="true">clipped corner wrapper · rotate(45deg) + diagonal translate unfurl · clip-path fold tabs · hover wiggle + sheen · real text</p>
</div>
</section>
```
## CSS
```css
.ac-23,
.ac-23 *,
.ac-23 *::before,
.ac-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-23 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-23-bg: oklch(0.97 0.006 250);
--ac-23-card: oklch(1 0 0);
--ac-23-ink: oklch(0.23 0.02 260);
--ac-23-mut: oklch(0.55 0.02 260);
--ac-23-line: oklch(0.23 0.02 260/.11);
background: var(--ac-23-bg);
color: var(--ac-23-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-23__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(16px,2.6vw,28px);
padding: clamp(20px,5vw,58px);
}
.ac-23__head {
display: grid;
justify-items: center;
gap: 9px;
text-align: center;
}
.ac-23__eyebrow {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: oklch(0.55 0.16 25);
}
.ac-23__title {
font-size: clamp(23px,3.4vw,38px);
font-weight: 650;
line-height: 1.12;
letter-spacing: -.03em;
text-wrap: balance;
}
.ac-23__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(196px,1fr));
gap: 16px;
width: min(100%,900px);
list-style: none;
}
.ac-23__card {
position: relative;
display: grid;
border: 1px solid var(--ac-23-line);
border-radius: 18px;
background: var(--ac-23-card);
box-shadow: 0 1px 2px oklch(0.23 0.02 260/.05);
overflow: hidden;
transition: transform .35s cubic-bezier(.16,1,.3,1),box-shadow .35s;
}
.ac-23__card:hover {
transform: translateY(-4px);
box-shadow: 0 22px 44px -26px oklch(0.23 0.02 260/.6);
}
.ac-23__corner {
position: absolute;
top: 0;
right: 0;
width: 124px;
height: 124px;
overflow: hidden;
border-radius: 0 18px 0 0;
z-index: 2;
pointer-events: none;
}
.ac-23__ribbon {
position: absolute;
top: 26px;
right: -44px;
width: 186px;
padding: 7px 0;
rotate: 45deg;
text-align: center;
background: var(--ac-23-c);
color: oklch(0.99 0 0);
font-size: 11.5px;
font-weight: 750;
letter-spacing: .15em;
text-transform: uppercase;
box-shadow: 0 6px 16px -8px color-mix(in oklch,var(--ac-23-c) 90%,oklch(0 0 0));
animation: ac-23-unfurl .72s cubic-bezier(.16,1,.3,1) both;
animation-delay: calc(.3s + var(--ac-23-i,0) * 110ms);
}
@keyframes ac-23-unfurl {
from {
translate: 62px -62px;
opacity: 0;
}
60% {
opacity: 1;
}
to {
translate: 0 0;
opacity: 1;
}
}
.ac-23__ribbon::before,
.ac-23__ribbon::after {
content: '';
position: absolute;
top: 100%;
width: 0;
height: 0;
border-style: solid;
border-color: color-mix(in oklch,var(--ac-23-c) 70%,oklch(0 0 0)) transparent transparent;
}
.ac-23__ribbon::before {
left: 34px;
border-width: 7px 7px 0 0;
}
.ac-23__ribbon::after {
right: 34px;
border-width: 7px 0 0 7px;
}
.ac-23__card:hover .ac-23__ribbon {
animation: ac-23-wiggle .55s cubic-bezier(.3,0,.3,1);
}
@keyframes ac-23-wiggle {
0%,
100% {
rotate: 45deg;
}
30% {
rotate: 43.6deg;
}
65% {
rotate: 46.2deg;
}
}
.ac-23__card:hover .ac-23__corner::after {
content: '';
position: absolute;
top: 26px;
right: -44px;
width: 186px;
height: 31px;
rotate: 45deg;
background: linear-gradient(105deg,transparent 36%,oklch(1 0 0/.5) 50%,transparent 64%);
animation: ac-23-sheen .8s ease-out;
pointer-events: none;
}
@keyframes ac-23-sheen {
from {
translate: -120px 0;
opacity: 0;
}
30% {
opacity: 1;
}
to {
translate: 120px 0;
opacity: 0;
}
}
.ac-23__media {
display: block;
aspect-ratio: 1;
background: linear-gradient(135deg,oklch(0.92 0.02 250),oklch(0.86 0.03 220));
}
.ac-23__media img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: scale .5s cubic-bezier(.16,1,.3,1);
}
.ac-23__card:hover .ac-23__media img {
scale: 1.06;
}
.ac-23__body {
display: grid;
gap: 5px;
padding: 14px 15px 17px;
}
.ac-23__name {
font-size: 14.5px;
font-weight: 640;
letter-spacing: -.02em;
}
.ac-23__price {
display: flex;
align-items: baseline;
gap: 8px;
font-variant-numeric: tabular-nums;
}
.ac-23__price b {
font-size: 16px;
font-weight: 700;
letter-spacing: -.025em;
}
.ac-23__price s {
font-size: 13px;
color: var(--ac-23-mut);
}
.ac-23__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-23-mut);
padding: 8px 15px;
border: 1px dashed var(--ac-23-line);
border-radius: 99px;
text-align: center;
max-width: min(100%,820px);
}
@media (prefers-reduced-motion: reduce) {
.ac-23__ribbon {
animation-duration: 1ms;
}
.ac-23__card:hover .ac-23__ribbon {
animation: none;
}
.ac-23__card:hover .ac-23__corner::after {
display: none;
}
.ac-23__card,
.ac-23__media img {
transition: none;
}
}
```How this works
A corner ribbon is a rotated bar in a clipped square. The wrapper does the clipping so the ribbon appears to pass under the card edge:
.corner { position: absolute; inset: 0 0 auto auto;
width: 128px; height: 128px; overflow: hidden;
border-radius: 0 18px 0 0 } /* match the card radius */
.ribbon { position: absolute; top: 26px; right: -46px;
width: 190px; rotate: 45deg; text-align: center }The entrance slides the ribbon along its own rotated axis, which is why it appears to unfurl from the corner rather than fly in from off-screen:
@keyframes ac-23-unfurl {
from { translate: 62px -62px; opacity: 0 } /* tucked behind the corner */
to { translate: 0 0; opacity: 1 }
}Because the element is already rotated 45°, a translate of (62, −62) is straight up the diagonal. The folded end-tabs are two small squares with clip-path: polygon() in a darker shade, sitting at each end — the detail that makes it read as fabric rather than a stripe.
Hover adds a 3-keyframe wiggle (±1.5°) and sends a highlight gradient along the ribbon. The text is real text inside the ribbon, so it is selectable, translatable and announced — a ribbon drawn with content: in CSS would be none of those.
Make it yours
- Ribbon type as a token:
--ac-23-cper state — sale red, new green, limited amber. One attribute swaps the whole treatment. - Left-corner variant: mirror with
inset: 0 auto auto 0,left: -46pxandrotate: -45deg. - Flag style: skip the rotation, use a horizontal bar with a
clip-path: polygon()notch on the trailing edge for a bookmark tag. - Stagger with the grid: give the ribbon
animation-delay: calc(var(--i) * 90ms + .35s)so ribbons land just after their cards from demo 01.
Gotchas — read before shipping
- Wrapper needs
overflow: hiddenand the card's corner radius, or the ribbon pokes past a rounded corner and looks broken. - Put that
overflow: hiddenon the corner wrapper only — never on the ribbon itself. The folded end-tabs are pseudo-elements attop: 100%, so a clip on the ribbon silently deletes them and you get a plain stripe. - Rotated text renders slightly soft; keep it at 11–12 px with bold weight and generous letter-spacing rather than trying to make it large.
- Never bake the ribbon text into a background image or a
content:string — it must be real text for translation, search and screen readers. - A ribbon overlapping the product image can hide the thing being sold. Keep it inside a corner safe area and test with your worst photo.
- z-index fights: the ribbon must sit above the media but below any focus ring or dropdown from the card.
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, individual rotate/translate properties and keyframe animation are Baseline Widely Available. color-mix() (Chrome 111 / Safari 16.2 / Firefox 113) is used for shading with a flat fallback.