02세일 태그 뱃지
clip-path 로 잘라낸 진짜 가격표 모양(구멍 포함) 뱃지가 살짝 흔들리며 커졌다 작아지길 반복해, 진짜 태그가 걸려 흔들리는 느낌을 준다.
쓰임할인율 강조 배지, 장바구니·상품 상세의 정가 옆 할인 표시
clip-path polygon@keyframes rotate+scalebox-shadow

<main class="stage is-demo">
<div class="tag" aria-label="30% off">
<span class="tag__hole"></span>
<span class="tag__text">-30%</span>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 2s — 흔들림 한 주기
$easing: $ease-spring;
$color: $stage-orange; // 세일 오렌지레드
@include stage($stage-ink);
.tag {
position: relative;
width: 132px; height: 92px;
// 진짜 가격표 모양 — 오른쪽이 뾰족한 오각형
clip-path: polygon(0 0, 76% 0, 100% 50%, 76% 100%, 0 100%);
background: $color;
display: grid; place-items: center;
box-shadow: $shadow-raised;
transform-origin: 8% 50%; // 규칙8: 구멍 핀 고정 흔들림
}
.tag__hole {
position: absolute;
left: $sp-2; top: 50%;
width: $sp-2; height: $sp-2;
margin-top: -$sp-1;
border-radius: 50%;
background: $stage-ink;
}
.tag__text {
color: #fff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 800;
font-size: 26px;
letter-spacing: -.01em;
padding-left: $sp-2;
}
.stage.is-demo .tag {
animation: tag-swing $duration $easing infinite;
}
@keyframes tag-swing {
0%, 100% { transform: rotate(0deg) scale(1); box-shadow: $shadow-raised; }
30% { transform: rotate(-6deg) scale(1.05); box-shadow: 0 18px 30px rgba(23, 20, 26, .4); }
60% { transform: rotate(3deg) scale(1.02); }
80% { transform: rotate(-1deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .tag { animation: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.tag {
position: relative;
width: 132px;
height: 92px;
clip-path: polygon(0 0, 76% 0, 100% 50%, 76% 100%, 0 100%);
background: #ff4d1f;
display: grid;
place-items: center;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
transform-origin: 8% 50%;
}
.tag__hole {
position: absolute;
left: 8px;
top: 50%;
width: 8px;
height: 8px;
margin-top: -4px;
border-radius: 50%;
background: #17141a;
}
.tag__text {
color: #fff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 800;
font-size: 26px;
letter-spacing: -0.01em;
padding-left: 8px;
}
.stage.is-demo .tag {
animation: tag-swing 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes tag-swing {
0%, 100% {
transform: rotate(0deg) scale(1);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
30% {
transform: rotate(-6deg) scale(1.05);
box-shadow: 0 18px 30px rgba(23, 20, 26, 0.4);
}
60% {
transform: rotate(3deg) scale(1.02);
}
80% {
transform: rotate(-1deg) scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .tag {
animation: none;
}
}import type { CSSProperties } from "react";
import styles from "./BadgeSalePercent.module.scss";
export type BadgeSalePercentProps = {
duration?: number;
easing?: string;
color?: string;
percent?: number;
};
export default function BadgeSalePercent({
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff4d1f",
percent = 30,
}: BadgeSalePercentProps) {
const vars: CSSProperties = { "--duration": `${duration}ms`, "--easing": easing, "--color": color } as CSSProperties;
return (
<div className={styles.tag} style={vars} aria-label={`${percent}% off`}>
<span className={styles.hole} />
<span className={styles.text}>-{percent}%</span>
</div>
);
}02 세일 태그 뱃지 — 측정 누적면적 3.387% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 뱃지 디자인 UI 9종 — 신상·세일·인증 배지 코드
이 데모가 속한 글
뱃지 디자인 UI 9종 — 신상·세일·인증 배지 코드뱃지 디자인 UI는 상품 썸네일이나 이름 옆에 작게 붙어 지금 상태를 알려주는 라벨 장식이다. NEW·세일·인증·품절처럼 사진 없이 코드 몇 줄로 만들 수 있고, 움직임 하나만 더하면 복붙 몇 줄로도 훨씬 눈에 띈다 이 글의 데모 전부 보기


