GODRICH

9 CSS Badge Design Ideas — New, Sale, Verified

A badge is the small label on a thumbnail or name that shows status at a glance — new, sale, verified, sold out.

Auto-plays · click a tile to jump to its section · all nine in one zip

The order below follows how a shopper actually meets these badges, not popularity. It opens with the first thing that catches an eye — new (01) and a price-shaking sale (02) — then moves to trust signals: verified (03) and an honest beta (04). From there it covers what's happening right now — live (05) and piling-up notifications (06) — before closing with proof of popularity (07) and the two warnings that end a purchase, low stock (08) and sold out (09).

01Corner ribbon badge

A ribbon tilted with rotate(-45deg) sits across the card corner, and a white light streak layered on top of it (an ::after gradient) sweeps across it on a loop by animating only background-position. Even a static NEW label keeps pulling the eye this way.

rotate(-45deg)::after 빛줄기 스윕overflow: hidden
.ribbon-wrap {
  position: absolute;
  top: $sp-6; left: -$sp-10;
  transform: rotate(-45deg);
}
.ribbon::after {
  background: linear-gradient(115deg, transparent 15%, rgba(255, 255, 255, .7) 45%, transparent 75%);
  background-size: 220% 100%;
}
.stage.is-demo .ribbon::after {
  animation: shine-sweep $duration $easing infinite;
}
@keyframes shine-sweep {
  0%, 15% { background-position: 140% 0; }
  55%, 100% { background-position: -40% 0; }
}

02Sale tag badge

clip-path cuts the badge into an actual price-tag silhouette, notch included, and it rocks and scales in a loop so it reads like a real paper tag swinging on a string.

clip-path polygon@keyframes rotate+scalebox-shadow
.tag {
  clip-path: polygon(0 0, 76% 0, 100% 50%, 76% 100%, 0 100%);
  transform-origin: 8% 50%;
}
.stage.is-demo .tag {
  animation: tag-swing $duration $easing infinite;
}
@keyframes tag-swing {
  0%, 100% { transform: rotate(0deg) scale(1); }
  30% { transform: rotate(-6deg) scale(1.05); }
  60% { transform: rotate(3deg) scale(1.02); }
  80% { transform: rotate(-1deg) scale(1); }
}

03Verified check badge

The verified badge beside the name is a single Phosphor SVG icon, and the moment it is confirmed transform: scale pops it 1 → 1.28 → 0.94 → 1 and settles.

currentColor@keyframes poparia-label
.check {
  transform-origin: center;
  color: $color;   /* Phosphor duotone 의 fill="currentColor" 가 이 값을 물려받는다 */
}
.stage.is-demo .check {
  animation: pop $duration $easing infinite;
}
@keyframes pop {
  0%, 50% { transform: scale(1); }
  62% { transform: scale(1.28); }
  74% { transform: scale(0.94); }
  86%, 100% { transform: scale(1); }
}

04Beta gradient pill badge

A purple gradient layer sits on top of the pill's base color, and only its opacity rises from 0 to 1 and back, so the two colors seem to bleed into each other across the BETA label.

겹친 레이어 opacity 크로스페이드position: absolute; inset: 0border-radius: $r-pill
.pill { position: relative; overflow: hidden; }
.pill__glow {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, $stage-yellow, #a855f7);
  opacity: 0;
}
.stage.is-demo .pill__glow {
  animation: glow-cross $duration $easing infinite;
}
@keyframes glow-cross {
  0%, 10% { opacity: 0; }
  50% { opacity: 1; }
  90%, 100% { opacity: 0; }
}

05Live pulse dot badge

A ::before circle behind the dot scales outward while its opacity fades to 0 on a loop, reading like a radar ping broadcasting outward from a live source.

::before scale·opacityaria-label@keyframes ping
.dot { position: relative; }
.dot::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: 50%;
  background: $color;
}
.stage.is-demo .dot::before {
  animation: ping $duration $easing infinite;
}
@keyframes ping {
  0% { transform: scale(1); opacity: .7; }
  70%, 100% { transform: scale(2.6); opacity: 0; }
}

06Notification count badge

The small circle springs from scale(0) past 1.15 and settles at 1, and right at that moment the bell icon itself rocks with a short rotate, signaling a notification just landed.

@keyframes scale bouncetransform-origin@media (prefers-reduced-motion)
.count {
  transform: scale(0);
}
.stage.is-demo .bell {
  animation: shake $duration $easing infinite;
}
.stage.is-demo .count {
  animation: pop-in $duration $easing infinite;
}
@keyframes pop-in {
  0%, 52% { transform: scale(0); }
  62% { transform: scale(1.15); }
  74%, 100% { transform: scale(1); }
}

07Trending flame badge

The flame SVG gets filter: brightness and hue-rotate nudged unevenly frame to frame, so the flicker never settles into a predictable beat, the way real fire doesn't either.

filter: hue-rotate·brightness다중 box-shadow 플리커@keyframes
.stage.is-demo .flame {
  animation: flicker $duration $easing infinite alternate;
}
@keyframes flicker {
  0% { filter: brightness(1) hue-rotate(0deg); transform: scale(1); }
  18% { filter: brightness(1.25) hue-rotate(-6deg); transform: scale(1.05); }
  40% { filter: brightness(.92) hue-rotate(4deg); transform: scale(.97); }
  63% { filter: brightness(1.15) hue-rotate(-3deg); transform: scale(1.03); }
  81% { filter: brightness(.97) hue-rotate(2deg); transform: scale(1); }
  100% { filter: brightness(1.2) hue-rotate(-5deg); transform: scale(1.04); }
}

08Limited-stock bar badge

A thin bar inside the badge shrinks scaleX from 1 toward 0.25 around transform-origin: left, while its background-color shifts from green to orange to red, so length and color drain together.

scaleX draintransform-origin: left배경색 다단 보간
.fill {
  transform: scaleX(1);
  transform-origin: left;
}
.stage.is-demo .fill {
  animation: drain $duration $easing infinite;
}
@keyframes drain {
  0%, 10% { transform: scaleX(1); background: $color; }
  50% { transform: scaleX(.6); background: $stage-orange; }
  90%, 100% { transform: scaleX(.25); background: $error; }
}

09Sold-out stripe overlay

A repeating-linear-gradient of diagonal stripes keeps scrolling across the whole card by animating background-position, while the SOLD OUT stamp in the middle breathes with a slow scale pulse.

repeating-linear-gradientbackground-position 애니메이션rgba 오버레이
.overlay {
  background: repeating-linear-gradient(-45deg,
    rgba($color, .82) 0 $sp-4,
    rgba($color, .62) $sp-4 #{$sp-4 * 2});
  background-size: #{$sp-4 * 4} #{$sp-4 * 4};
}
.stage.is-demo .overlay {
  animation: stripes-flow $duration $easing infinite;
}
@keyframes stripes-flow {
  0% { background-position: 0 0; }
  100% { background-position: #{$sp-4 * 4} 0; }
}

Where this breaks — motion that leans on repaint vanishes on camera

Badge 04's gradient sheen originally moved by sliding background-position. To the eye it clearly flowed, but comparing the same stage frame by frame showed almost no difference between consecutive frames — background-position leaves its repaint timing entirely to the browser, and when that falls out of step with the capture, the change sinks below the threshold. Layering a purple sheet on top and animating only its opacity from 0 to 1 kept the same effect sharp in every frame. Pick properties that run on the compositor, like opacity and transform, over ones that need a repaint, like background position or a spreading shadow, and what you see stops disagreeing with what the recording keeps. The password for the zip is wpxqsgtk, typed exactly as shown, and it opens all nine folders at once. Badge 03's verified check went the same way — instead of drawing a stroke in, the whole icon pops with transform: scale.

Accessibility

All nine drop to animation: none under prefers-reduced-motion: reduce. Badges that loop forever, like 07's flame flicker and 09's stripe overlay, still read correctly at rest — color and shape carry the meaning on their own. Badge 03's checkmark simply stays at its natural scale(1) when motion is off, so the verified mark itself never disappears. The full spec for this setting lives in MDN's prefers-reduced-motion page.

FAQ

Can I build a badge like this without any image files?

Yes — all nine use only CSS, plus a short inline SVG icon for badges 03, 06, and 07. Backgrounds, shadows, and clip-path shapes are enough to draw ribbons, tag notches, and diagonal stripes. More CSS-only decorations turn up under site search if you look for "badge".

Can I swap these colors for my own brand?

Yes, for most of them. Every demo keeps a single $color variable at the top of its SCSS (03's check icon is fill="currentColor", so it inherits that same color), and the React version exposes the same thing as a color prop — change that one value and the whole badge follows. Badge 08, which shifts color across several stages, needs each stage inside its @keyframes updated too.

My badge animation looks washed out in a screen recording. Why?

Change which property moves. Background position and spreading shadows need a repaint, so the browser decides when they land and that can fall out of step with the capture, while opacity and transform run on the compositor and survive frame by frame. First time on this site? The about page explains how these demos get built and checked before anything ships.

Enter the archive password

The password is inside this article. You will find it as you read.