09Badge tĩnh cho prefers-reduced-motion
Chấm trạng thái vốn đập dìu dịu bằng box-shadow và scale, nhưng khi prefers-reduced-motion được phát hiện thì đúng chỗ đó đổi thành badge 'Live' tĩnh, không một chút chuyển động.
Dùng choChỉ báo thay thế cho người dùng giảm chuyển động, badge trạng thái đạt chuẩn accessibility
box-shadowprefers-reduced-motionscale

<main class="stage">
<div class="rb-badge is-demo">
<span class="rb-dot"></span>
<span class="rb-text">Generating</span>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: 1.6s;
$easing: $ease-out;
$color: $subject-blue;
@include stage($stage-ink);
.rb-badge {
display: inline-flex;
align-items: center;
gap: $sp-2;
padding: $sp-2 $sp-4;
border-radius: $r-pill;
background: rgba(255, 255, 255, .08);
color: $subject-cream;
font: 600 12px/1 -apple-system, "Segoe UI", sans-serif;
}
.rb-dot {
width: $sp-2;
height: $sp-2;
border-radius: $r-pill;
background: $color;
}
// 기본: box-shadow 로 번지는 맥박. prefers-reduced-motion: reduce 에서는 애니메이션을 끄고
// 점 자체를 고정 크기로 두어(정적 뱃지) 같은 자리에서 움직임 없이 "생성 중" 임을 전달한다
@keyframes rb-pulse {
0% { box-shadow: 0 0 0 0 rgba(47, 109, 246, .55); transform: scale(1); }
70% { box-shadow: 0 0 0 8px rgba(47, 109, 246, 0); transform: scale(1.15); }
100% { box-shadow: 0 0 0 0 rgba(47, 109, 246, 0); transform: scale(1); }
}
.rb-badge.is-demo .rb-dot {
animation: rb-pulse $duration $easing infinite;
}
@media (prefers-reduced-motion: reduce) {
.rb-badge.is-demo .rb-dot {
animation: none !important;
box-shadow: none !important;
transform: none !important;
}
}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);
}
.rb-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
color: #fff7e6;
font: 600 12px/1 -apple-system, "Segoe UI", sans-serif;
}
.rb-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: #2f6df6;
}
@keyframes rb-pulse {
0% {
box-shadow: 0 0 0 0 rgba(47, 109, 246, 0.55);
transform: scale(1);
}
70% {
box-shadow: 0 0 0 8px rgba(47, 109, 246, 0);
transform: scale(1.15);
}
100% {
box-shadow: 0 0 0 0 rgba(47, 109, 246, 0);
transform: scale(1);
}
}
.rb-badge.is-demo .rb-dot {
animation: rb-pulse 1.6s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.rb-badge.is-demo .rb-dot {
animation: none !important;
box-shadow: none !important;
transform: none !important;
}
}import { CSSProperties } from "react";
import styles from "./ReducedMotionBadge.module.scss";
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export interface ReducedMotionBadgeProps {
duration?: string;
easing?: string;
color?: string;
label?: string;
}
export default function ReducedMotionBadge({
duration = "1.6s",
easing = "ease-out",
color = "#2f6df6",
label = "Generating",
}: ReducedMotionBadgeProps) {
const style: Vars = {
"--duration": duration,
"--easing": easing,
"--color": color,
};
return (
<div className={styles.badge} style={style}>
<span className={styles.dot} />
<span className={styles.text}>{label}</span>
</div>
);
}09 Badge tĩnh cho prefers-reduced-motion — Diện tích đo được 0.278% · khung hình chuyển động 19/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css loading animation cho AI chat, copy-paste
Bài viết chứa demo này
9 css loading animation cho AI chat, copy-pasteBa chấm nhấp nhô, con trỏ chớp tắt, chữ hiện dần lúc bạn chờ câu trả lời trong khung chat AI như ChatGPT hay Claude đều Xem tất cả demo trong bài này


