Shimmering cards with a diagonal shine + a rosette-friendly corner cut-out — built only with CSS.
Modern UI often needs subtle flair without sacrificing performance. In this guide we’ll combine two small but high-impact techniques:
- A shine animation that softly glides across the card on hover.
- A corner cut-out that creates space for a “Popular”/“Pro” badge.
Both are pure CSS, accessible, and production-friendly.
TL;DR
- Animate a large, rotated overlay gradient with
transform
for smooth GPU-accelerated motion. - Create a corner cut-out using either a pseudo-element + box-shadow trick (broad compatibility) or a CSS mask (cleaner, modern).
- Respect
prefers-reduced-motion
and ensure readable contrast.
Minimal HTML Skeleton
<div class="card">
<div class="shine" aria-hidden="true"></div>
<div class="badge">Popular</div>
<div class="content">
<!-- your card content -->
</div>
<div class="cutout" aria-hidden="true"></div>
</div>
No frameworks needed — we’ll focus on CSS only.
The Shine Animation
The shine is a large diagonal band moving across the card. We render it as an absolutely positioned overlay, then translate it from corner to corner.
.card {
position: relative;
overflow: hidden;
border-radius: 22px;
background: linear-gradient(33deg, #dff69b 0%, #95e79b 35%, #80de8e 70%, #a9f9b2 100%);
}
/* the moving band */
.shine {
position: absolute;
inset: 0;
width: 200%;
height: 200%;
pointer-events: none;
opacity: 0;
transition: opacity 300ms ease;
background: linear-gradient(
180deg,
rgba(248,255,228,0) 0%,
rgba(248,255,228,0.8) 50%,
rgba(248,255,228,0) 100%
);
transform: translate(-100%, -100%) rotate(45deg);
z-index: 1;
}
.card:hover .shine {
opacity: 1;
animation: shine 3s linear infinite;
}
@keyframes shine {
from { transform: translate(-100%, -100%) rotate(45deg); }
to { transform: translate(200%, 200%) rotate(45deg); }
}
Why width/height: 200% and rotate(45deg)?
200% size ensures the band covers the card completely during the whole travel — no clipped edges.
45° creates a neat diagonal “sweep” that feels natural on rectangular cards.
Performance notes
Animate transform, not background-position. transform leverages GPU and remains smooth.
Keep the overlay non-interactive with pointer-events: none.
Consider will-change: transform only if you actually see performance hiccups.
Reduced motion
@media (prefers-reduced-motion: reduce) {
.card:hover .shine {
animation: none;
opacity: .35; /* static highlight */
}
}
The Corner Cut-Out
We’ll cover two approaches:
A) Pseudo-element + Box-Shadow Trick (robust, easy to theme)
We fake an “erased” corner by placing a rounded square pseudo-element and pushing a white (or page background) shadow inward, so it looks like a chunk is missing.
.cutout {
--corner-size: 32px;
--corner-radius: 14px;
--offset-right: 20px;
--offset-top: 3px;
--shadow-1: 1px;
--shadow-2: 4px;
position: absolute;
top: var(--offset-top);
right: var(--offset-right);
width: 85px; /* total cut-out area (helps control the composition) */
height: 38px;
pointer-events: none;
background: transparent;
overflow: hidden;
z-index: 2; /* above the gradient bg, below content if needed */
}
.cutout::before,
.cutout::after {
content: "";
position: absolute;
width: var(--corner-size);
height: var(--corner-size);
top: 1px;
right: 20px;
border-bottom-left-radius: var(--corner-radius);
background: transparent;
}
.cutout::before { box-shadow: -1px 1px white; }
.cutout::after { box-shadow: -4px 5px white; }
If your page background isn’t white, replace white with a variable that matches the actual outer background.
B) Real Hole with CSS Mask (clean, modern)
Masks let you truly subtract pixels so the page background shows through.
.card {
--r: 14px; /* radius */
-webkit-mask: radial-gradient(
circle var(--r) at right var(--r) top var(--r),
transparent calc(var(--r) - 0.5px),
#000 calc(var(--r) + 0.5px)
);
mask: radial-gradient(
circle var(--r) at right var(--r) top var(--r),
transparent calc(var(--r) - 0.5px),
#000 calc(var(--r) + 0.5px)
);
}
The tiny 0.5px buffer helps anti-aliasing on different pixel densities. Test on Safari; older versions might need the -webkit- prefix only.
Layering: Z-Index & Hit Testing
Keep the content above backgrounds (z-index > shine/cut-out).
The shine should be visually above the background but not clickable.
For the badge, place it near the cut-out and ensure it sits above the shine (e.g., z-index: 3).
.badge {
position: absolute;
top: 10px;
right: 20px;
font: 600 12px/1 system-ui, sans-serif;
text-transform: uppercase;
z-index: 3;
}
.content { position: relative; z-index: 2; }
Theming with CSS Variables
Centralize constants for easy design tweaks:
:root {
--radius: 22px;
--badge-h: 35px;
--badge-w: 95px;
/* cut-out */
--corner-size: 32px;
--corner-radius: 14px;
/* shine */
--shine-angle: 45deg;
--shine-duration: 3s;
}
Common Pitfalls & Fixes
Band “tears” at edges: Ensure overlay is bigger than the card (200%) and fully traverses the diagonal.
Gradient banding: Use 3+ color stops or add a subtle translucent white layer (as in the examples) to smooth it out.
Wrong cut-out color: For the pseudo-element trick, make the shadow color match the actual page background.
Mask aliasing in Safari: Use a tiny ±0.5px tolerance around the radius.
Copy-Paste Snippets
Minimal Shine
.card { position: relative; overflow: hidden; border-radius: 22px; }
.shine {
position: absolute; inset: 0; width: 200%; height: 200%;
background: linear-gradient(180deg, transparent, rgba(255,255,255,.8), transparent);
transform: translate(-100%,-100%) rotate(45deg);
opacity: 0; pointer-events: none;
}
.card:hover .shine { opacity: 1; animation: shine 3s linear infinite; }
@keyframes shine {
from { transform: translate(-100%,-100%) rotate(45deg); }
to { transform: translate(200%,200%) rotate(45deg); }
}
Minimal Cut-Out (pseudo-element)
.cutout{position:absolute;top:3px;right:40px;width:85px;height:38px;pointer-events:none}
.cutout::before,.cutout::after{
content:"";position:absolute;top:1px;right:20px;width:32px;height:32px;border-bottom-left-radius:14px;background:transparent
}
.cutout::before{box-shadow:-1px 1px white}
.cutout::after{box-shadow:-4px 5px white}
Minimal Cut-Out (mask)
.card{
--r:14px;
-webkit-mask:radial-gradient(circle var(--r) at right var(--r) top var(--r),
transparent calc(var(--r) - .5px), #000 calc(var(--r) + .5px));
mask:radial-gradient(circle var(--r) at right var(--r) top var(--r),
transparent calc(var(--r) - .5px), #000 calc(var(--r) + .5px));
}
Top comments (0)