04표 스켈레톤
머리글은 남기고 셀만 막대로.
쓰임대시보드 표
6행열 폭 유지줄무늬

<main class="stage">
<div class="table">
<div class="row head"><span>이름</span><span>상태</span><span>값</span><span>날짜</span></div>
<div class="body">
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<span class="sheen" aria-hidden="true"></span>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 빛이 표 몸통을 훑는 시간
$easing: $ease-out;
$color: $subject-ink; // 셀 색 (흰 무대라 잉크 톤)
@include stage($stage-paper);
.table {
width: clamp(220px, 88%, 380px);
border-radius: $r-card;
overflow: hidden;
background: rgba($color, .03);
box-shadow: $shadow-press;
}
// 열 폭은 헤더와 몸통이 같은 grid-template-columns 를 써서 유지한다
.row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: clamp(6px, 2vw, 10px);
padding: clamp(6px, 2vw, 9px) clamp(10px, 3vw, 14px);
align-items: center;
}
.row.head {
font: 600 clamp(10px, 2.8vw, 12px)/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: rgba($color, .7);
border-bottom: 1px solid rgba($color, .12);
}
.body {
position: relative; // .sheen 의 위치 기준 (빠지면 엉뚱한 곳에 그려진다)
overflow: hidden; // 빛이 몸통 밖(헤더 포함)으로 안 나가게
}
.body .row:nth-child(even) { background: rgba($color, .03); }
.cell {
height: clamp(9px, 2.6vw, 11px);
border-radius: $r-control;
background: rgba($color, .13);
}
// ── 빛 한 줄기가 몸통만 훑는다. 헤더 글자는 그대로 둔다 ──────────
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($subject-blue, .45), transparent);
transform: translateX(-120%);
animation: shimmer $duration $easing infinite;
pointer-events: none;
}
@keyframes shimmer {
to { transform: translateX(120%); }
}
@media (prefers-reduced-motion: reduce) {
.sheen { animation: none !important; opacity: 0; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.table {
width: clamp(220px, 88%, 380px);
border-radius: 16px;
overflow: hidden;
background: rgba(23, 20, 26, 0.03);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: clamp(6px, 2vw, 10px);
padding: clamp(6px, 2vw, 9px) clamp(10px, 3vw, 14px);
align-items: center;
}
.row.head {
font: 600 clamp(10px, 2.8vw, 12px)/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: rgba(23, 20, 26, 0.7);
border-bottom: 1px solid rgba(23, 20, 26, 0.12);
}
.body {
position: relative;
overflow: hidden;
}
.body .row:nth-child(even) {
background: rgba(23, 20, 26, 0.03);
}
.cell {
height: clamp(9px, 2.6vw, 11px);
border-radius: 12px;
background: rgba(23, 20, 26, 0.13);
}
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(47, 109, 246, 0.45), transparent);
transform: translateX(-120%);
animation: shimmer 2s ease-out infinite;
pointer-events: none;
}
@keyframes shimmer {
to {
transform: translateX(120%);
}
}
@media (prefers-reduced-motion: reduce) {
.sheen {
animation: none !important;
opacity: 0;
}
}import type { CSSProperties } from "react";
import styles from "./TableSkeleton.module.scss";
export type TableSkeletonProps = {
/** 빛이 표 몸통을 훑는 시간 (기본 2000ms) */
duration?: number;
/** CSS easing (기본 ease-out) */
easing?: string;
/** 셀 색 */
color?: string;
/** 머리글 (열 폭은 이 배열 길이로 정해진다) */
columns?: string[];
/** 몸통 행 수 (기본 6) */
rows?: number;
className?: string;
};
export default function TableSkeleton({
duration = 2000,
easing = "ease-out",
color = "#17141a",
columns = ["이름", "상태", "값", "날짜"],
rows = 6,
className = "",
}: TableSkeletonProps) {
const vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color } as CSSProperties;
const template = { gridTemplateColumns: `2fr repeat(${columns.length - 1}, 1fr)` } as CSSProperties;
return (
<div className={`${styles.table} ${className}`} style={vars}>
<div className={`${styles.row} ${styles.head}`} style={template}>
{columns.map((c) => <span key={c}>{c}</span>)}
</div>
<div className={styles.body}>
{Array.from({ length: rows }).map((_, r) => (
<div key={r} className={styles.row} style={template}>
{columns.map((_, c) => <span key={c} className={styles.cell} />)}
</div>
))}
<span className={styles.sheen} aria-hidden="true" />
</div>
</div>
);
}04 표 스켈레톤 — 측정 누적면적 45.848% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 스켈레톤 UI CSS 9종 — 복붙하면 되는 로딩 화면
이 데모가 속한 글
스켈레톤 UI CSS 9종 — 복붙하면 되는 로딩 화면스켈레톤 UI CSS는 실제 글자·이미지가 도착하기 전에 그 자리를 회색 막대나 동그라미로 미리 그려 두는 로딩 방식입니다. 빈 화면이나 뱅글뱅글 도는 스피너보다 "곧 뭔가 나온다"는 확신을 먼저 줍니다. 아래 아홉 이 글의 데모 전부 보기


