Groo Design

Skeleton

불러오는 동안 곧 나타날 내용의 자리를 미리 그리는 회색 면. 목록 · 카드 · 상세처럼 구조가 정해진 자리에 쓴다 — 화면의 틀(머리 · 탭 · 제목 · 버튼 · 카드 면)은 실제로 그리고, 서버에서 올 데이터 자리만 스켈레톤이다. 저장 · 새로 고침처럼 행동 하나를 기다리는 자리는 Progress Circle, 이미지가 없거나 못 불러온 자리는 Content Placeholder 다.

구조는 당근 SEED Skeleton(Apache-2.0)을 따른다 — 면 하나 위로 흰 띠가 자기 폭만큼 지나가는 반짝임, 모서리 넷(0 · 8 · 16 · full), 글자 자리는 글줄 높이. 깜빡임(펄스)은 두지 않는다. 기다리는 동안의 시간표(1초 · 5초 · 10초)도 이 문서의 "기다리는 동안" 에 둔다 — SEED Loading 패턴의 시나리오를 규칙으로 옮겼다. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-03 사용자 결정). 옛 Skeleton 스펙(깜빡임 · 모서리 4 · 글줄보다 낮은 막대)을 대신한다.

수치 원본은 skeleton.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

9:41
가계부
10월 지출
최근 거래
9:41
카드 혜택
받을 수 있는 혜택
9:41
거래 상세
금액
카테고리
결제 수단
날짜
9:41
가계부
10월 지출
최근 거래
9:41
카드 혜택
받을 수 있는 혜택
9:41
거래 상세
금액
카테고리
결제 수단
날짜
가계부 · 카드 혜택 · 거래 상세를 불러오는 동안 — 라이트 · 다크

직접 골라 보기

모양(글 · 카드 · 아바타 · 사진) · 글자 크기 · 모션 줄이기 · 기다린 시간을 고르면 스펙대로 그린 스켈레톤과 그 코드가 바뀐다. 기다린 시간을 옮기면 1초 · 5초 · 10초에 화면이 어떻게 바뀌는지 볼 수 있다.

최근 메모
1.5초 — 스켈레톤 — 숨은 상태 글 "불러오는 중…"
모양
글자 크기 text — 높이는 그 글자의 줄 높이
모션 줄이기
모드

1초까지는 틀만 그리고 자리를 지킨다 · 5초에 "평소보다 오래 걸리고 있어요." · 10초면 실패(요청 제한 — 다시 시도는 그 안에서 2번, 1초 · 2초 뒤).

import { LoadingRegion, Skeleton } from "@/components/ui/skeleton"
import { ResultSection } from "@/components/ui/result-section"

<LoadingRegion
  pending={query.isPending}
  failed={query.isError && !query.data}
  fallback={<Skeleton text="t4" className="w-40" />}
  failure={<ResultSection kind="failure" size="medium" title="메모를 불러오지 못했어요" primaryAction={{ label: "다시 시도", onClick: () => query.refetch() }} />}
>
  {content}
</LoadingRegion>

Anatomy

최근 거래

평소보다 오래 걸리고 있어요.

ⓐ Region — 기다리는 영역(쿼리 하나)ⓑ Root — 면ⓒ Shimmer — 면 위를 지나는 띠ⓓ Slow Text — 5초부터 한 줄

면 bg-neutral-weak · 띠는 면과 같은 크기로 왼쪽 밖(-100%)에서 오른쪽 밖(100%)으로 지나간다 — 그림은 지나가는 한 순간이다. 스켈레톤은 보조 기술에 숨기고(aria-hidden), 영역은 aria-busy · 화면의 상태 글 하나가 알린다

기다리는 영역 안에 면과 그 위를 지나는 반짝임 띠, 5초부터 오래 걸림 글
부위설명
ⓐ Region기다리는 영역 — 데이터 자리 하나(쿼리 하나). 시간표 · 요청 제한 · 낭독을 맡는다.
ⓑ Root면 — 곧 나타날 내용 하나의 자리.
ⓒ Shimmer반짝임 띠 — 면 위를 자기 폭만큼 지나간다.
ⓓ Slow Text오래 걸림 글 — 5초부터 한 줄.

부위

부위설명
root면 — 곧 나타날 내용 하나의 자리(글 한 줄 · 숫자 · 카드 · 아바타 · 사진)
shimmer반짝임 띠 — 면과 같은 크기, 자기 폭만큼 왼쪽 밖에서 오른쪽 밖으로 지나간다
region기다리는 영역 — 데이터 자리 하나(쿼리 하나). 시간표 · 요청 제한 · 낭독을 맡는다
slowText오래 걸림 글 — 5초부터 "평소보다 오래 걸리고 있어요."

Properties

모서리

곧 올 내용의 모양을 따른다 — 글 · 숫자 8 (기본), 그림 자리(썸네일 · 카드 그림)는 Image Frame 의 모서리(폭 24 이하 4 · 48 이하 6 · 그 위 8 — 다 받은 그림과 같은 모서리), 목록 앞 타일 12(List 타일 · Logo Tile 40 — 거래 · 카테고리 · 자산 줄), 카드 면 16(Card 모양 자리 전체), 아바타 · 원 아이콘 · 칩 full, 화면 끝에 붙는 사진 0. 카드 그림(신용카드 그림)은 카드 면이 아니라 그림이라 4 · 6 · 8 이다.

8 · radius-r2(기본)글 · 숫자글 · 숫자 · 작은 조각 · 폭 49 이상의 그림 자리(카드 그림 56 · 격자 · 상세) — 기본
4 · radius-r1그림 24폭 24 이하의 그림 자리 — Image Frame 모서리
6 · radius-r1_5그림 40폭 25 ~ 48 의 그림 자리 — 목록 앞 썸네일 40 · 48(Image Frame 모서리)
8 · radius-r2카드 그림 112글 · 숫자 · 작은 조각 · 폭 49 이상의 그림 자리(카드 그림 56 · 격자 · 상세) — 기본
12 · radius-r3목록 앞 타일 40목록 앞 타일 — List 의 타일 · Logo Tile 40(크기 × 0.3)과 같은 모서리
16 · radius-r4카드 면카드 면 — Card 모양 자리 전체. 카드 그림(신용카드 그림)은 그림이라 4 · 6 · 8
full · radius-full아바타아바타 · 원 아이콘 · 칩
0 · 0px화면 폭 사진화면 폭 사진 — 화면 끝에 붙는 그림(상세 위 사진)
모서리 — 글 8 · 그림 4 · 6 · 8(폭으로) · 타일 12 · 카드 면 16 · 아바타 full · 화면 폭 사진 0

모서리

radius모서리설명
00px — 화면 끝에 붙는 사진 — 상세 위 사진. 담는 틀에 모서리가 있으면 그 틀과 같게화면 폭 사진 — 화면 끝에 붙는 그림(상세 위 사진)
4radius-r1 4px — Image Frame — 폭 24 이하. 다 받은 그림과 같은 모서리폭 24 이하의 그림 자리 — Image Frame 모서리
6radius-r1_5 6px — Image Frame — 폭 25 ~ 48(목록 앞 썸네일 40 · 48)폭 25 ~ 48 의 그림 자리 — 목록 앞 썸네일 40 · 48(Image Frame 모서리)
8 (기본)radius-r2 8px — 글 · 숫자 · 작은 조각, Image Frame 폭 49 이상글 · 숫자 · 작은 조각 · 폭 49 이상의 그림 자리(카드 그림 56 · 격자 · 상세) — 기본
12radius-r3 12px — List 타일 · Logo Tile 40 의 모서리와 같다 — 거래 · 카테고리 · 자산 줄의 앞 자리(줄이 통째로 기다릴 때 — 이름을 알면 Logo Tile 은 첫 글자부터 그린다)목록 앞 타일 — List 의 타일 · Logo Tile 40(크기 × 0.3)과 같은 모서리
16radius-r4 16px — 카드 면 — Card 모양 자리 전체. 그림 자리(썸네일 · 카드 그림)는 Image Frame 의 4 · 6 · 8카드 면 — Card 모양 자리 전체. 카드 그림(신용카드 그림)은 그림이라 4 · 6 · 8
fullradius-full 9999px아바타 · 원 아이콘 · 칩

크기 — 글은 글줄 높이

크기는 그 자리에 올 내용의 크기다. 글 · 숫자 자리는 그 글자의 줄 높이만큼 높다 — 14 글자(t4)면 19, 16 글자(t5)면 22. 글자보다 낮은 막대로 그리면 글이 오는 순간 줄이 밀린다. 글 줄의 폭은 실제 글 길이와 비슷하게 두고, 여러 줄이면 마지막 줄을 짧게 둔다.

t2 · 12 / 16점심 식사 12,000원
t4 · 14 / 19점심 식사 12,000원
t5 · 16 / 22점심 식사 12,000원
t7 · 20 / 27점심 식사 12,000원
글자 자리 = 글줄 높이 — 12 · 14 · 16 · 20 글자 옆에 같은 높이의 스켈레톤

면

속성값비고
너비내용의 폭그 자리에 올 내용의 폭 — 글 줄은 비슷한 글 길이, 여러 줄이면 마지막 줄을 짧게(60 ~ 80%)
높이내용의 높이글 · 숫자는 그 글자의 줄 높이 — t2 12 → 16 · t3 13 → 18 · t4 14 → 19 · t5 16 → 22 · t7 20 → 27. 글로 바뀌어도 세로가 움직이지 않는다
배경color-bg-neutral-weak #F5F6FA · 다크 #353B4D흰 면 위 1.08 · 다크 1.30(떠 있는 면 1.13) — 흰 면(bg-layer-default · bg-layer-floating) 위에만 둔다. 페이지 바탕(bg-layer-basement) 위에서는 1.00 으로 사라진다

반짝임

면 위로 흰 띠가 자기 폭만큼 왼쪽 밖에서 오른쪽 밖으로 1.5초에 지나가고, 쉬지 않고 되풀이한다. 다크에서는 띠가 훨씬 옅다(흰 10%). 깜빡임(투명도 1 ↔ 0.5)은 두지 않는다. 모션 줄이기면 띠가 멈추고 면만 남는다(v104).

라이트 — 띠가 지나간다
다크 — 흰 띠가 훨씬 옅다
모션 줄이기 — 띠가 멈추고 면만
−100%
−50%
0%
50%
100%

띠는 면과 같은 폭으로 translateX −100% → 100% — 1.5초 · cubic-bezier(0.35, 0, 0.35, 1), 쉬는 틈 없이 되풀이한다. 같은 화면의 스켈레톤은 한 박자로 지난다

반짝임 — 1.5초 · 모션 줄이기면 면만

반짝임 띠

속성값비고
너비100%면과 같은 크기 — 면 밖은 잘린다(면 모양 그대로)
배경gradient-shimmer-neutral · 다크 gradient-shimmer-neutral-dark흰 띠 — 가운데(46 ~ 54%)가 흰 67%(다크 10%), 양 끝은 투명

State

상태모습
enabled기다리는 중 — 띠가 지나간다
reducedMotion모션 줄이기 — 띠가 멈추고 면만 남는다

상태

상태shimmer 배경shimmer 불투명도
enabled 기다리는 중 — 띠가 지나간다gradient-shimmer-neutral · 다크 gradient-shimmer-neutral-dark — 흰 띠 — 가운데(46 ~ 54%)가 흰 67%(다크 10%), 양 끝은 투명—
reducedMotion 모션 줄이기 — 띠가 멈추고 면만 남는다gradient-shimmer-neutral · 다크 gradient-shimmer-neutral-dark — 흰 띠 — 가운데(46 ~ 54%)가 흰 67%(다크 10%), 양 끝은 투명0 — 띠를 멈추고 지운다(v104) — 면만 남는다. 흰 면 위 1.08 이라 자리는 옅게 보이고, 상태는 숨은 상태 글이 알린다

모션

전환시간곡선대상비고
반짝임motion-duration-loop 1500msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)translateX −100% → 100%쉬는 틈 없이 되풀이 — 같은 화면의 스켈레톤은 한 박자로 지난다(늦게 붙은 것도 같은 시계를 따른다). 모션 줄이기면 멈춘다(v104)
내용으로 바뀜motion-duration-d3 150msmotion-ease-enter cubic-bezier(0, 0, 0.15, 1)opacity 0 → 1스켈레톤을 걷고 내용을 투명도로 — Result Section 의 결과로 바뀜과 같다. 자리 크기는 그대로

Guidelines

틀은 먼저, 데이터 자리만 스켈레톤

머리 · 탭 · 섹션 제목 · 버튼 · 필터 · 카드 면처럼 로컬 상태와 정해진 글로 그릴 수 있는 틀은 처음부터 실제로 그린다. 스켈레톤은 서버에서 올 데이터 자리에만 둔다 — 틀까지 덮으면 바로 보일 정보를 가리고, 데이터가 오는 순간 틀이 진짜로 바뀌며 화면이 튄다.

  • 내용과 스켈레톤은 한 묶음이다 — 같은 부모 안에서 갈라 껍데기(카드 · 섹션 · 여백)를 함께 쓴다. 로딩 분기가 껍데기를 따로 그리면 두 벌이 되어 한쪽만 고쳐진다.
  • 껍데기는 실제로 쓰는 컴포넌트(Card · List · 섹션)를 그대로 쓴다 — 자체 상자로 흉내 내지 않는다. 컴포넌트에 값이 하나 늘면 흉내 낸 쪽만 옛 모양으로 남는다.
  • 자기 쿼리를 가진 컴포넌트는 자기 스켈레톤을 갖는다 — 부모 페이지가 다시 흉내 내지 않는다.
  • 데이터에 따라 있고 없고가 갈리는 섹션은 스켈레톤에 넣지 않는다 — 불러오는 동안은 뜰지 알 수 없다. 빼 두면 데이터가 와서 아래에 나타나는 쪽이 덜 튄다.
  • 화면 전체가 쿼리 하나를 기다려도 앱 틀(상단 바 · 탭 바 · 사이드바)은 그린다 — 틀이 있는데 틀 없이 화면 가운데 원 하나, 덮을 콘텐츠가 없는 자리의 회색 막 "Loading" 은 두지 않는다. 예외는 틀이 생기기 전(스플래시) — 아래 "무엇으로 기다리나".
9:41
가계부
전체지출수입이체
최근 거래
이렇게 — 화면 제목 · 필터 · 섹션 제목 · 버튼 · 탭 바는 처음부터 그리고, 서버에서 올 거래 자리만 스켈레톤
9:41
이렇게 하지 않는다 — 제목 · 필터 · 버튼까지 회색으로 — 바로 보일 틀을 가리고, 데이터가 오는 순간 틀이 바뀌며 화면이 튄다
틀은 실제로 · 데이터 자리만 스켈레톤 — 탭 · 제목 · 버튼까지 덮은 화면

흰 면 위에만

스켈레톤 면(bg-neutral-weak)은 흰 면(카드 · 시트 · 대화상자 — bg-layer-default · bg-layer-floating) 위에서 1.08:1 로 보이고 띠가 지나가며 모양이 드러난다. 회색 페이지 바탕(bg-layer-basement) 위에서는 같은 색이라 사라진다(1.00:1) — 바탕 위의 자리는 카드 면을 먼저 그리고 그 안에 스켈레톤을 둔다.

이렇게 — 흰 면(카드 · 시트 · 대화상자) 위 — 면과 1.08:1(다크 1.30:1), 띠가 지나가며 모양이 드러난다
스켈레톤 세 줄이 여기 있다
이렇게 하지 않는다 — 회색 페이지 바탕 위에 바로 — 바탕과 1.00:1, 같은 색이라 사라진다. 카드 면을 먼저 그린다
흰 카드 위 · 회색 바탕 위 — 바탕 위에서는 사라진다

모양은 내용대로

곧 올 내용의 줄 수 · 길이 · 모서리를 따른다 — 목록이면 실제 줄 높이로 몇 줄, 메모 본문이면 여러 줄에 마지막 줄을 짧게, 아바타는 원, 사진은 그 비율. 실제 줄과 높이가 1px 만 달라도 데이터가 올 때 목록 전체가 밀린다. 글자 모양까지 흉내 내지는 않는다 — 자리만 그린다.

  • 점심 식사식비 · 현대카드 M−12,000원
이렇게 — 줄 높이 66 그대로 — 글 자리는 그 글자의 줄 높이(제목 22 · 메타 18), 앞 원은 full. 데이터가 와도 아무것도 밀리지 않는다
  • 점심 식사식비 · 현대카드 M−12,000원
이렇게 하지 않는다 — 글자보다 낮은 막대 — 줄이 60 라 데이터가 오면 줄마다 6 씩, 네 줄이면 24 밀린다
모양 — 실제 줄과 같은 높이 · 글자보다 낮은 막대

Progress Circle 과 한 자리에는 되도록 하나

한 자리에는 되도록 하나만 — 구조가 보이는 넓은 자리(목록 · 카드 · 상세)는 스켈레톤, 행동 하나를 기다리는 작은 자리(저장 · 새로 고침 · 목록 끝 더 불러오기)는 Progress Circle 이다. 스켈레톤 위에 원을 얹지 않는다(SEED "Progress Circle과 같이 사용하는 것은 최대한 지양해요").

SkeletonProgress Circle(값 없는 원)
보여 주는 것곧 올 내용의 구조(줄 · 카드 모양)지금 작동 중이라는 사실만
자리내용이 실제로 놓일 자리기다리는 범위의 가운데 · 제목 옆 · 목록 끝
어울리는 일넓은 자리를 한 번에 받기 · 반복되는 줄행동 하나(새로 고침 · 더 불러오기 · 저장)
어울리는 시간(SEED)1 ~ 10초1 ~ 4초
groo 시간표둘이 같다 — 1초부터 보임 · 5초 안내 글 · 10초 실패(아래 "기다리는 동안")같다
최근 거래
이렇게 — 구조가 보이는 자리는 스켈레톤 하나
최근 거래
이렇게 하지 않는다 — 스켈레톤 위에 원을 얹는다 — 두 가지가 같은 기다림을 겹쳐 말한다
한 자리에 하나 — 스켈레톤 위에 원을 얹은 화면

기다리는 동안

불러오는 모든 자리(첫 진입 · 화면 안 조회 · 달 · 기간 · 필터 바꾸기)는 이 시간표를 따른다 — 스켈레톤이든 Progress Circle 이든 같다. 결과(비어 있음 · 실패)는 Result Section 이 그린다. 수치는 skeleton.yaml 의 region 이다.

시간표 — 1초 · 5초 · 10초

지난 시간화면보조 기술
0 ~ 1초틀만 — 데이터 자리는 비워 두고 높이는 지킨다. 1초 안에 오면 아무것도 깜빡이지 않는다영역에 aria-busy="true" — 아직 아무것도 읽지 않는다
1초 ~스켈레톤 또는 Progress Circle"불러오는 중…"(숨은 상태 글)
5초 ~그대로 + 글 한 줄 "평소보다 오래 걸리고 있어요."그 글을 한 번 읽는다
10초실패 — Result Section failure + "다시 시도"Result Section 이 알린다(role="status")
다 옴내용(투명도 150ms) · 비어 있으면 Result Section emptyaria-busy 를 풀고 상태 글을 비운다
0초1초5초10초
틀만스켈레톤 · 원+ 오래 걸림 글실패 + 다시 시도
최근 거래
0 ~ 1초틀만 — 데이터 자리는 보이지 않게 그려 높이를 지킨다(점선)
최근 거래
1초 ~스켈레톤 — 숨은 상태 글 "불러오는 중…"
최근 거래

평소보다 오래 걸리고 있어요.

5초 ~안내 글 한 줄 — 한 번 읽힌다
최근 거래
거래를 불러오지 못했어요

잠시 후 다시 시도해주세요.

10초요청 제한 — 실패 + 다시 시도(Result Section)
시간표 — 0 ~ 1초 틀만 · 1초 스켈레톤 · 5초 안내 글 · 10초 실패 + 다시 시도

기다리는 영역

속성값비고
보이기 시작1000ms처음 1초는 틀만 — 데이터 자리는 비워 두고(보이지 않게 그려 높이는 지킨다) 스켈레톤 · 원을 보이지 않는다. 1초 안에 오면 아무것도 깜빡이지 않는다
오래 걸림 글5000ms오래 걸림 글을 더한다 — 스켈레톤 · 원은 그대로. 보조 기술에 한 번 정중하게 읽힌다
요청 제한10000ms요청 제한 — 처음 요청부터 다시 시도까지 합쳐 10초. 넘으면 끊고 실패(Result Section failure + "다시 시도"). 올리기 · 받기는 진행이 10초 멈추면 실패
다시 시도2번읽기(불러오기)만 · 1초 · 2초 뒤 · 10초 안에서만. 다시 해도 같은 오류(4xx — 권한 · 없음 · 잘못된 요청)와 쓰기(저장 · 삭제)는 다시 보내지 않는다

오래 걸림 글

5초가 지나도 안 오면 기다리는 자리에 글 한 줄을 더한다 — "평소보다 오래 걸리고 있어요." 스켈레톤 · 원은 그대로 둔다. 스켈레톤 영역이면 첫 스켈레톤 위에 왼쪽 맞춤으로, 가운데 원이면 원 아래에 가운데 맞춤으로 16 띄워 둔다. 목록 카드(body="list") 안의 영역이면 이 글과 실패 Result Section 이 좌우 24 를 갖는다 — 목록 카드는 줄마다 24 를 갖고 카드 여백이 없어, 카드 안 글이 늘 24 에 서게 한다(Card 23B). 내용 카드 안이면 카드의 24 가 맡는다. 영역 하나에 한 줄이고, 화면이 통째로 기다리면 콘텐츠 영역 맨 위 한 줄이다. 보조 기술에는 한 번 정중하게 읽힌다(아래 "알리기").

최근 거래

평소보다 오래 걸리고 있어요.

스켈레톤 영역첫 스켈레톤 위 16 · 왼쪽 맞춤
검색 결과

평소보다 오래 걸리고 있어요.

가운데 원원 아래 16 · 가운데 맞춤
5초 — 스켈레톤 위 · 원 아래의 안내 글

오래 걸림 글

속성값비고
글자text-t4 14px / 19px
굵기400
글자·아이콘color-fg-neutral-muted #535866 · 다크 #B7BDCC문장으로 된 상태 문구라 muted(2026-10-16 사용자 결정 글자 역할 D5 — 그대로 둔다)
간격spacing-x4 16px스켈레톤 영역은 첫 스켈레톤 위 16(왼쪽 맞춤), 가운데 원이면 원 아래 16(가운데 맞춤). 영역 하나에 한 줄 — 단어 단위 줄바꿈(v114)
좌우 여백0px목록 카드(body list) 바로 아래 영역이면 24 — 목록 카드는 줄마다 24 를 갖고 카드 여백이 없다. 실패 Result Section 도 같다(Card 23B). 내용 카드 안이면 카드의 24 가 맡는다

요청 제한 10초 · 다시 시도는 그 안에서

불러오기는 10초 안에 끝낸다 — 처음 요청부터 다시 시도까지 합쳐 10초가 지나면 끊고 실패로 그린다. 저절로 다시 시도하는 것은 그 10초 안에서만이다.

  • 다시 시도는 읽기(불러오기)만 2번까지, 1초 · 2초 뒤에 한다. 10초를 넘기게 되면 하지 않는다.
  • 다시 해도 같은 오류(권한 없음 · 없는 데이터 · 잘못된 요청 — 4xx)는 다시 하지 않고 바로 실패를 그린다.
  • 저장 · 삭제 같은 쓰기는 저절로 다시 보내지 않는다 — 두 번 들어간다. 쓰기의 실패는 그 자리에서 알린다(폼 맨 위 Callout).
  • 올리기 · 받기처럼 진행을 아는 긴 일은 다 끝나는 시간이 아니라 진행이 10초 동안 멈추면 실패다 — 그동안은 값 있는 Progress Circle 로 진행을 보인다.
  • 실패 화면의 "다시 시도" 를 누르면 시간표를 처음부터 다시 센다 — 그동안 버튼에 로딩을 건다(Result Section).

무엇으로 기다리나

기다리는 동안의 틀 규칙은 이 표 한 곳이다 — Progress Circle 은 이 표를 가리킨다(2026-10-22 사용자 결정 5B — https://claude.ai/artifact/NuEPiLRH2fJidXdKzcDsCX). 덮개 칸은 뒤 화면을 덮는지다. 다 온 뒤 · 그림 자리에 무엇을 보일지(비어 있음 · 실패 · 대체 그림 · 이니셜)는 Content Placeholder 의 "대신 보이는 것 고르기" 다.

이런 때보이는 것덮개
화면 · 섹션의 첫 진입(목록 · 카드 · 상세 — 구조를 앎)스켈레톤 — 틀은 그리고 데이터 자리만없음
구조를 미리 그릴 수 없는 자리(검색 결과 · 결과 화면 전체)Progress Circle 40 — 틀은 그리고 콘텐츠 영역 가운데(SEED "콘텐츠가 없는 첫 로딩은 원만 화면 중앙" 과 같은 뜻)없음
앱 틀이 생기기 전(스플래시)로고 묶음(나무 + 서비스 이름) 아래 원 — brand 톤(DESIGN.md "그룹 색 · 로고 칠")없음
달 · 기간 · 필터를 바꿈(다른 내용)바뀔 숫자 · 목록 자리만 스켈레톤 — 아래 "다른 내용을 받을 때"없음
같은 내용을 다시 받음(화면으로 돌아옴 · 당겨서 새로 고침)보던 내용을 그대로 둔다 — 실패는 Snackbar, 아래 "같은 내용을 다시 받을 때"없음
섹션 하나를 새로 고침 — 뒤를 계속 써도 됨섹션 제목 옆 Progress Circle 24, 내용은 그대로 — 자리가 범위를 말한다없음
목록 끝 더 불러오기목록 아래 가운데 Progress Circle 24없음
저장 · 제출누른 버튼의 로딩(Button)없음
올리기 · 받기(진행을 안다)값 있는 Progress Circle 24 — 사진이면 딤 위 흰 원그 사진 위 딤만
뒤를 만지면 안 되는 처리(가져오기 확정 · 이용 해지 처리)화면 가운데 흰 원 40 — 안내 글 없음(Progress Circle 의 "뒤를 만지면 안 되는 처리")화면 전체 딤
이미지Image Frame 이 그린다 — 같은 모서리의 스켈레톤을 처음부터(위 시간표의 0 ~ 1초 틀만을 기다리지 않는다 — 빈 칸이 없다), 받아 둔 그림은 스켈레톤 없이 바로. 시간표에서 따르는 것은 10초 실패다 — 없거나 실패하면 Content Placeholder(2026-10-22 사용자 결정 4B). 물건 타일(Logo Tile)은 이름을 아니까 첫 글자부터없음
최근 거래
첫 진입스켈레톤 — 틀은 그리고 데이터 자리만
최근 거래
  • 점심 식사식비−12,000원
  • 스타벅스카페−5,600원
섹션 새로 고침제목 옆 원 24 — 내용은 그대로
거래
  • 점심 식사식비−12,000원
  • 스타벅스카페−5,600원
목록 끝 더 불러오기목록 아래 가운데 원 24
메모를 저장하는 중이에요.
저장 · 제출누른 버튼의 로딩 — 폭은 그대로
자리마다 — 첫 진입 스켈레톤 · 섹션 새로 고침 원 24 · 목록 끝 원 · 저장 버튼 로딩

다른 내용을 받을 때 — 달 · 기간 · 필터

달 · 기간 · 필터를 바꾸면 머리(고른 달 이름 · 기간 칩)와 고정 틀은 바로 바뀌고, 바뀔 숫자 · 목록 자리만 기다린다. 옛 달의 숫자는 그 순간 지운다 — 새 달 이름 아래 옛 숫자를 남기지 않는다(돈 숫자라 잘못 읽힌다). 지운 자리는 시간표를 따른다 — 1초까지는 비워 두고(높이는 지킨다) 그 뒤 스켈레톤이다. 이미 받아 둔 달이면 바로 보인다.

9:41
통계
2026년 9월
지출
식비
교통
쇼핑
이렇게 — 고른 달 이름은 바로 9월 — 바뀔 숫자 자리만 스켈레톤, 10월 숫자는 지운다
9:41
통계
2026년 9월
지출656,500원
식비432,000원
교통128,500원
쇼핑96,000원
이렇게 하지 않는다 — 9월 머리 아래 10월 숫자 — 새 값이 올 때까지 돈 숫자가 잘못 읽힌다
9월로 넘김 — 머리는 바로 9월, 숫자 · 목록 자리만 스켈레톤 · 9월 머리 아래 10월 숫자

같은 내용을 다시 받을 때

화면으로 돌아오거나 당겨서 새로 고칠 때는 보던 내용을 지우지 않는다 — 새 값이 오면 바뀌고, 실패하면 내용을 둔 채 Snackbar 로 가볍게 알린다(Result Section 의 결정, 2026-10-02). 스켈레톤으로 돌아가지 않는다. 당겨서 새로 고침은 Progress Circle 의 원이 새로 고침이 끝날 때까지 돈다.

9:41
가계부
최근 거래
  • 점심 식사식비 · 현대카드 M−12,000원
  • 스타벅스카페 · 현대카드 M−5,600원
  • 지하철교통 · 국민 체크카드−1,450원
이렇게 — 당겨서 새로 고침 — 원이 돌고 보던 내용은 그대로(88 아래로 내려와 머문다)
9:41
가계부
최근 거래
  • 점심 식사식비 · 현대카드 M−12,000원
  • 스타벅스카페 · 현대카드 M−5,600원
  • 지하철교통 · 국민 체크카드−1,450원
새로 고치지 못했어요. 잠시 후 다시 당겨주세요.
이렇게 — 실패해도 내용을 둔 채 스낵바로 가볍게 알린다
9:41
가계부
최근 거래
이렇게 하지 않는다 — 다시 받는다고 스켈레톤으로 돌아간다 — 보던 내용이 사라지고 화면이 깜빡인다
다시 받기 — 보던 내용 그대로 · 실패는 스낵바 · 스켈레톤으로 돌아간 화면

알리기 — 보조 기술

  • 기다리는 영역에 aria-busy="true" 를 단다 — 다 오거나 실패하면 푼다.
  • 상태 글은 화면에 하나다(role="status" · aria-live="polite", 보이지 않게) — 1초에 "불러오는 중…", 5초에 "평소보다 오래 걸리고 있어요." 를 한 번씩 읽는다. 함께 기다리는 영역이 여럿이어도 한 번만 읽는다. 다 오면 비운다. 실패 · 비어 있음은 Result Section 이 알린다.
  • 상태 글은 영역보다 먼저 있어야 읽힌다 — 빈 채로 두었다가 1초에 글을 넣는다.
  • 스켈레톤 자체는 보조 기술에 숨긴다(aria-hidden) — 회색 면에는 읽을 것이 없다.
  • 앱(Flutter)은 같은 일을 Semantics(liveRegion: true) 로 한다 — 스켈레톤은 ExcludeSemantics.

코드

레시피 recipes/shadcn/components/ui/skeleton.tsx 를 쓴다. 조각은 Skeleton, 기다리는 영역은 LoadingRegion(시간표 · aria-busy · 오래 걸림 글 · 실패로 바꿈)이고, 앱 맨 위에 LoadingAnnouncer 를 한 번 둔다(화면의 상태 글 하나). 아래 미리보기는 스펙 값으로 그린 모습이다.

  • Skeleton — radius("0" · "4" · "6" · "8" 기본 · "12" · "16" · "full" — 그림 자리는 Image Frame 의 imageFrameRadius(폭) 으로 고른다), text("t1" ~ "t14" — 그 글자의 줄 높이를 높이로, 모서리 8), 크기 · 폭은 className. 늘 aria-hidden 이고, 글 자리(span · p) 안에도 둘 수 있게 span(블록)으로 그린다.
  • LoadingRegion — pending(처음 받는 중 · 내용 없음) · failed(내용 없이 실패) · fallback(스켈레톤, 또는 "circle" — 영역 가운데 Progress Circle 40) · failure(실패 때 — Result Section) · children(내용). 1초까지 fallback 을 보이지 않게 그려 높이를 지키고, 5초에 오래 걸림 글을 더하고, pending 동안 aria-busy 를 단다. 내용이 이미 있으면(같은 내용을 다시 받는 중) pending 이 아니다 — 내용을 그대로 둔다.
  • LoadingAnnouncer — 화면의 상태 글 하나. LoadingRegion 들이 여기에 알리고, 같은 글은 한 번만 읽는다.
  • useWaitPhase(pending) — "quiet"(0 ~ 1초) · "waiting"(1초 ~) · "slow"(5초 ~). 영역을 직접 짤 때 쓴다.
  • LOADING_TIMING — { showAfter: 1000, slowAfter: 5000, timeout: 10000, retryDelays: [1000, 2000] }(skeleton.yaml 의 region).

목록 — 틀은 그리고 데이터 자리만

최근 거래
  • 점심 식사식비 · 현대카드 M−12,000원
  • 스타벅스카페 · 현대카드 M−5,600원
  • 지하철교통 · 국민 체크카드−1,450원
  • 월급수입 · 토스뱅크 통장+3,200,000원
눌러서 다시 불러온다
import { LoadingRegion, Skeleton } from "@/components/ui/skeleton"
import { Card, CardHeader, CardTitle } from "@/components/ui/card"
import { List, ListItem } from "@/components/ui/list"
import { ResultSection } from "@/components/ui/result-section"

// 줄 껍데기는 실제 List 그대로 — 글 자리는 text 로 그 글자의 줄 높이(t5 22 · t3 18)
function TransactionRowsSkeleton({ rows }: { rows: number }) {
  return (
    <List>
      {Array.from({ length: rows }, (_, i) => (
        <ListItem
          key={i}
          prefix={<Skeleton radius="12" className="size-10" />}
          title={<Skeleton text="t5" className="w-32" />}
          detail={<Skeleton text="t3" className="w-20" />}
          suffix={<Skeleton text="t5" className="w-16" />}
        />
      ))}
    </List>
  )
}

<Card body="list">
  <CardHeader><CardTitle>최근 거래</CardTitle></CardHeader>{/* 틀 — 처음부터 그린다. 목록 카드는 줄이 가장자리까지(card.md) */}
  <LoadingRegion
    pending={query.isPending}
    failed={query.isError && !query.data}
    fallback={<TransactionRowsSkeleton rows={4} />}
    failure={
      <ResultSection
        kind="failure"
        size="medium"
        title="거래를 불러오지 못했어요"
        description="잠시 후 다시 시도해주세요."
        primaryAction={{ label: "다시 시도", onClick: () => query.refetch() }}
      />
    }
  >
    <TransactionList items={query.data} />
  </LoadingRegion>
</Card>

모양 다섯

<Skeleton text="t4" className="w-40" />                  {/* 글 — 높이 19, 모서리 8(기본) */}
<Skeleton radius="6" className="size-10" />               {/* 썸네일 40 — Image Frame 모서리(폭 48 이하 6) */}
<Skeleton radius="16" className="h-28 w-full" />          {/* 카드 면 */}
<Skeleton radius="full" className="size-10" />            {/* 아바타 */}
<Skeleton radius="0" className="aspect-[4/3] w-full" />  {/* 화면 폭 사진 */}

다른 달 — 머리는 바로, 숫자 자리만

2026년 10월
지출
412,300원

‹ 를 눌러 지난달로 — 달 이름은 바로 바뀌고 금액 자리만 기다린다(1초까지는 비워 둔다). 받아 둔 달로 돌아오면 바로 보인다.

// 달 이름(틀)은 고른 달을 바로 보이고, 숫자는 그 달의 쿼리를 기다린다.
// 옛 달 값을 이어서 보이지 않는다 — placeholderData 를 쓰지 않는다
const stats = useQuery({ queryKey: ["stats", month], queryFn: () => getStats(month) })

<MonthNav value={month} onValueChange={setMonth} />
<LoadingRegion
  pending={stats.isPending}
  failed={stats.isError && !stats.data}
  fallback={<Skeleton text="t9" className="w-36" />}
  failure={<ResultSection kind="failure" size="medium" title="통계를 불러오지 못했어요" primaryAction={{ label: "다시 시도", onClick: () => stats.refetch() }} />}
>
  <Amount value={stats.data?.total} />
</LoadingRegion>

앱 맨 위 · 요청 설정

import { LOADING_TIMING, LoadingAnnouncer } from "@/components/ui/skeleton"

// 화면의 상태 글 하나 — LoadingRegion 들이 여기에 알린다(같은 글은 한 번만 읽는다)
<LoadingAnnouncer>
  <App />
</LoadingAnnouncer>

// 요청은 제품 코드다(레시피 밖) — 읽기는 2번까지 1초 · 2초 뒤, 4xx 와 쓰기는 다시 보내지 않는다.
// 한 번의 불러오기는 다시 시도까지 합쳐 LOADING_TIMING.timeout(10초) 안에 끝낸다
new QueryClient({
  defaultOptions: {
    queries: {
      retry: (count, error) => count < LOADING_TIMING.retryDelays.length && isRetryable(error),
      retryDelay: (count) => LOADING_TIMING.retryDelays[count] ?? LOADING_TIMING.retryDelays[LOADING_TIMING.retryDelays.length - 1] ?? 0,
    },
    mutations: { retry: false },
  },
})

Behavior

인터랙션동작
불러오기 시작영역에 aria-busy — 1초까지 틀만(데이터 자리는 보이지 않게 그려 높이를 지킨다)
1초스켈레톤이 보이고 띠가 지나간다 — 상태 글 "불러오는 중…"
5초오래 걸림 글이 더해진다 — 한 번 읽힌다
10초실패 — Result Section failure + "다시 시도" 로 바뀐다(투명도 150ms)
다 옴스켈레톤을 걷고 내용이 투명도로 나타난다(150ms) — 자리 크기는 그대로
모션 줄이기띠가 멈추고 면만 남는다 — 시간표 · 상태 글은 그대로
누르기 · 키보드없다 — 스켈레톤은 초점을 받지 않는다

Accessibility

기준검증
WCAG 1.4.11 Non-text contrast(≥ 3:1)스켈레톤은 장식(aria-hidden)이라 기준 밖 — 흰 면 위 1.08 · 다크 1.30(떠 있는 면 1.13), 띠가 지나가며 모양이 드러난다. 기다리는 상태는 상태 글이 알린다
WCAG 2.2.2 Pause, Stop, Hide진행을 알리는 반복이라 기준의 예외지만, 10초면 실패로 바뀌어 멈추고 모션 줄이기면 처음부터 멈춘다 ✓
WCAG 2.3.3 Animation from Interactions모션 줄이기면 띠를 멈춘다(v104) ✓
WCAG 4.1.3 Status messages영역 aria-busy, 화면의 상태 글 하나(role="status") — 1초 "불러오는 중…" · 5초 "평소보다 오래 걸리고 있어요." 를 한 번씩, 결과는 Result Section ✓
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)누르는 것이 없다 — 해당 없음
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)누르는 것이 없다 — 해당 없음
ARIA스켈레톤 aria-hidden="true", 영역 aria-busy="true"(다 오면 뺀다), 상태 글 role="status" + aria-live="polite". 영어("Loading") · 세 점("...")을 쓰지 않는다 — "불러오는 중…"(Writing v106)

Do / Don't

✅ Do

  • 틀(머리 · 탭 · 제목 · 버튼 · 카드 면)은 그리고 데이터 자리만 스켈레톤.
  • 글 자리는 그 글자의 줄 높이, 모서리는 내용대로(글 8 · 그림 4 · 6 · 8 · 타일 12 · 카드 면 16 · 아바타 full · 화면 폭 사진 0).
  • 흰 면(카드 · 시트 · 대화상자) 위에.
  • 1초까지 기다렸다 보이고, 5초에 한 줄, 10초에 실패 + 다시 시도.
  • 달을 바꾸면 머리는 바로, 바뀔 숫자 · 목록 자리만 기다린다.

❌ Don't

  • 깜빡임(펄스) · 브랜드 색 띠.
  • 회색 페이지 바탕 위에 바로 — 사라진다.
  • 탭 · 제목 · 버튼까지 회색으로 덮기.
  • 스켈레톤 위에 원을 얹기 · 한 자리에 둘 다(되도록 하나만).
  • 새 달 이름 아래 옛 달 숫자.
  • 불러오는 중인 금액 자리를 "—" 로 — 값이 없는 것과 같아 보인다.
  • 실패하는 조회를 스켈레톤으로 오래 붙잡기(재시도 10번에 41초).

Specification

skeleton.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Skeleton 을 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.

Base

부위상태속성값
rootenabled높이height내용의 높이글 · 숫자는 그 글자의 줄 높이 — t2 12 → 16 · t3 13 → 18 · t4 14 → 19 · t5 16 → 22 · t7 20 → 27. 글로 바뀌어도 세로가 움직이지 않는다
너비width내용의 폭그 자리에 올 내용의 폭 — 글 줄은 비슷한 글 길이, 여러 줄이면 마지막 줄을 짧게(60 ~ 80%)
배경backgroundbg-neutral-weak#F5F6FA다크 #353B4D흰 면 위 1.08 · 다크 1.30(떠 있는 면 1.13) — 흰 면(bg-layer-default · bg-layer-floating) 위에만 둔다. 페이지 바탕(bg-layer-basement) 위에서는 1.00 으로 사라진다
shimmerenabled너비width100%면과 같은 크기 — 면 밖은 잘린다(면 모양 그대로)
배경backgroundgradient-shimmer-neutrallinear-gradient(90deg, #ffffff00 0%, #ffffffab 46%, #ffffffab 54%, #ffffff00 100%)다크 gradient-shimmer-neutral-darklinear-gradient(90deg, #ffffff00 0%, #ffffff1a 46%, #ffffff1a 54%, #ffffff00 100%)흰 띠 — 가운데(46 ~ 54%)가 흰 67%(다크 10%), 양 끝은 투명
regionenabled보이기 시작showAfter1000ms처음 1초는 틀만 — 데이터 자리는 비워 두고(보이지 않게 그려 높이는 지킨다) 스켈레톤 · 원을 보이지 않는다. 1초 안에 오면 아무것도 깜빡이지 않는다
오래 걸림 글slowAfter5000ms오래 걸림 글을 더한다 — 스켈레톤 · 원은 그대로. 보조 기술에 한 번 정중하게 읽힌다
요청 제한timeout10000ms요청 제한 — 처음 요청부터 다시 시도까지 합쳐 10초. 넘으면 끊고 실패(Result Section failure + "다시 시도"). 올리기 · 받기는 진행이 10초 멈추면 실패
다시 시도retry2번읽기(불러오기)만 · 1초 · 2초 뒤 · 10초 안에서만. 다시 해도 같은 오류(4xx — 권한 · 없음 · 잘못된 요청)와 쓰기(저장 · 삭제)는 다시 보내지 않는다
slowTextenabled좌우 여백paddingX0px목록 카드(body list) 바로 아래 영역이면 24 — 목록 카드는 줄마다 24 를 갖고 카드 여백이 없다. 실패 Result Section 도 같다(Card 23B). 내용 카드 안이면 카드의 24 가 맡는다
간격gapspacing-x416px스켈레톤 영역은 첫 스켈레톤 위 16(왼쪽 맞춤), 가운데 원이면 원 아래 16(가운데 맞춤). 영역 하나에 한 줄 — 단어 단위 줄바꿈(v114)
글자·아이콘foregroundfg-neutral-muted#535866다크 #B7BDCC문장으로 된 상태 문구라 muted(2026-10-16 사용자 결정 글자 역할 D5 — 그대로 둔다)
글자typographytext-t414px / 19px
굵기fontWeight400
shimmerreducedMotion불투명도opacity0띠를 멈추고 지운다(v104) — 면만 남는다. 흰 면 위 1.08 이라 자리는 옅게 보이고, 상태는 숨은 상태 글이 알린다

radius=0

부위상태속성값
rootenabled모서리radius0px화면 끝에 붙는 사진 — 상세 위 사진. 담는 틀에 모서리가 있으면 그 틀과 같게

radius=4

부위상태속성값
rootenabled모서리radiusradius-r14pxImage Frame — 폭 24 이하. 다 받은 그림과 같은 모서리

radius=6

부위상태속성값
rootenabled모서리radiusradius-r1_56pxImage Frame — 폭 25 ~ 48(목록 앞 썸네일 40 · 48)

radius=8

부위상태속성값
rootenabled모서리radiusradius-r28px글 · 숫자 · 작은 조각, Image Frame 폭 49 이상

radius=12

부위상태속성값
rootenabled모서리radiusradius-r312pxList 타일 · Logo Tile 40 의 모서리와 같다 — 거래 · 카테고리 · 자산 줄의 앞 자리(줄이 통째로 기다릴 때 — 이름을 알면 Logo Tile 은 첫 글자부터 그린다)

radius=16

부위상태속성값
rootenabled모서리radiusradius-r416px카드 면 — Card 모양 자리 전체. 그림 자리(썸네일 · 카드 그림)는 Image Frame 의 4 · 6 · 8

radius=full

부위상태속성값
rootenabled모서리radiusradius-full9999px(알약)

SEED 와 다른 점

  • 면은 groo bg-neutral-weak(gray-200 #F5F6FA · 다크 #353B4D) — SEED gray-200(#f3f4f5 · #1d2025)의 역할 짝이다. 흰 면 위 1.08:1 로 SEED(1.10)와 거의 같다.
  • 흰 면 위에만 둔다 — groo gray-200 은 페이지 바탕과 같은 색이라 바탕 위에서는 사라진다(SEED 는 정하지 않았다).
  • 모션 줄이기면 띠가 멈춘다(v104) — SEED 는 계속 돈다.
  • 보조 기술에 알린다 — 영역 aria-busy + 화면의 상태 글 하나. SEED 웹은 아무것도 남기지 않는다.
  • 모서리 기본값 8 — SEED 코드와 같다(rootage 는 0 을 기본이라 적었다).
  • 썸네일 자리는 Image Frame 의 모서리(4 · 6 · 8) — SEED Skeleton 표는 "카드 및 썸네일 16" 이지만, 같은 문서 그림의 상품 썸네일은 약 8 이고 SEED Image Frame 은 폭으로 4 · 6 · 8 이다. 다 받은 그림과 같은 모서리로 둔다 — 16 은 카드 면만.
  • magic(AI) 톤은 두지 않는다 — 그 띠(gradient)도 들이지 않았다(v104).
  • 기다리는 동안의 시간표 · 요청 제한 · 다시 시도를 수치로 둔다 — SEED 는 Loading 패턴의 시나리오(5초 안내 · 10초 실패)로만 적었고 구현에는 장치가 없다.

Migration notes

2026-10-22 — 그림 자리는 처음부터 · 대신 보이는 것은 한 곳

"무엇으로 기다리나" 의 이미지 줄을 코드대로 맞췄다 — 그림 자리의 스켈레톤은 처음부터(시간표의 0 ~ 1초 틀만을 기다리지 않는다), 받아 둔 그림은 바로, 시간표에서 따르는 것은 10초 실패다(content-placeholder.md 의 "시간표를 따른다" 와 image-frame.yaml 의 "처음부터 깐다" 가 어긋나 있었다). 다 온 뒤 무엇을 보일지는 Content Placeholder 의 "대신 보이는 것 고르기" 한 곳을 가리킨다. 사용자 결정 4B · 1B — https://claude.ai/artifact/KPp9Lpd3v5KMgNrDn7pZmt. 값 변경 없음.

2026-10-22 — 기다림 틀 규칙은 이 표 한 곳

"무엇으로 기다리나" 를 덮개 칸이 있는 상황 표로 바꾸고 Progress Circle 이 이 표를 가리키게 했다. 스플래시(틀이 생기기 전 — 가운데 원만) 예외와 뒤를 만지면 안 되는 처리(화면 전체 딤 + 흰 원 40) 줄을 더했다. Skeleton · Progress Circle 견주기 표를 두고, "같이 쓰지 않는다" 를 SEED 말투대로 "되도록 하나만" 으로 바꿨다 — progress-circle.md 의 두 문장이 서로 부딪치던 것(Tone 의 스플래시 ↔ 틀 없는 원 금지)을 푼다(사용자 결정 5B · 4B — https://claude.ai/artifact/NuEPiLRH2fJidXdKzcDsCX). 값 변경 없음.

2026-10-03 — SEED Skeleton 으로 새로 둔다

사용자가 비교 페이지에서 정했다 — SEED 반짝임 그대로(면 bg-neutral-weak · 흰 띠 · 1.5초 · 곡선 (0.35, 0, 0.35, 1) · 모서리 글 8 · 카드 16 · 원 full · 사진 0 · 글자 자리 = 글줄 높이), 시간표는 SEED Loading(1초 안 틀만 · 5초 안내 글 · 10초 실패 + 다시 시도 · 요청 제한 10초), 다른 달 · 기간은 머리를 바로 바꾸고 숫자 자리만 스켈레톤. 한 단계 진한 면(gray-300)은 고르지 않았다. 옛 Skeleton(깜빡임 기본 · surface-input · 모서리 4 · 글줄보다 낮은 막대 · 브랜드 25% 띠)은 걷었다 — 옛 스펙은 skeleton.history/v-pre-seed-loading.*.

제품은 앱 적용 단계에서 옮긴다(2026-10-03 조사 — Desk 웹 · HR 은 크로미움에 띄워 쟀고, Desk 앱은 위젯 테스트로 쟀다).

  • Desk 웹 — 공용 Skeleton(shared/ui/skeleton.tsx:11-21, 508개 · 41파일)은 깜빡임 2초 · #f0f2f7 · 모서리 4 로 페이지 바탕 위 1.04:1 · 카드 위 1.12:1 이고 모션 줄이기에도 계속 깜빡인다(skeleton.tsx:17). 반짝임(skeleton.tsx:27-42, 브랜드 25% 띠)은 쓰는 곳이 0. 캘린더 손 스켈레톤 31개(widgets/calendar/ui/{agenda-view,week-and-day-view,year-view}/*-skeleton.tsx). 가계부 줄 스켈레톤 64 ↔ 실제 65(ExpensePage.tsx:254-286). 불러오는 중인 금액 자리 "—" 9곳(DashboardPage.tsx:1189 · 1288 · 1321 · 1355 · 2190, ExpensePage.tsx:540 · 568 · 598, AssetPage.tsx:1691). 첫 진입은 앱 틀 없이 가운데 원 하나(app/router/routes.tsx:102-111), 지연 로딩 페이지 이동은 표시가 없다. 증권 구독 확인 중 빈 화면(features/subscription/ui/SecuritiesGate.tsx:19-22), 증권 스켈레톤의 aria-busy + 이름이 이름 금지 요소에(TossStocksPage.tsx:409 · 425 · 767 · 771, daily-quote-table.tsx:117-118). 요청 제한이 없고(shared/api/base.ts) 5xx · 네트워크만 1번 다시 시도(shared/api/retry.ts:14-21).
  • Desk 앱 — PSkeleton(shared/widgets/p_skeleton.dart:16-119, 308개 · 38파일 · 화면 스켈레톤 46)은 깜빡임 2초 · #f0f2f7 · 모서리 4, 스켈레톤마다 컨트롤러가 따로라 엇갈려 깜빡인다(p_skeleton.dart:53-58). 의미 트리가 비어 있다(증권 4곳만 이름 — stocks/toss_stocks_view.dart:2352 · 2450 · 2471 · 2774). riverpod 기본 재시도 10번이 전역에 켜져(app/session_scope.dart:74-80) 0.3초 만에 실패하는 조회가 41초 동안 스켈레톤이다. .when 38파일(예: expense/expense_screen.dart:271-279)은 달을 바꾸면 화면 통째 스켈레톤으로 돌아가고, 새로 고침이 실패하면 보던 내용을 지운다. PDayGroupSkeleton 64 ↔ 65(shared/widgets/p_day_group.dart:136-162).
  • HR 웹 — shadcn Skeleton(shared/ui/shadcn/skeleton.tsx:3-11, 398개 · 53파일)은 bg-accent #f8f8f8 로 흰 바탕 위 1.06:1, 모서리 6, 모션 줄이기에도 깜빡인다. 재시도 3번 약 7초 · 요청 제한 없음(app/providers/QueryProvider.tsx:14-20), 연도 · 필터를 바꿀 때마다 새 키로 다시 스켈레톤. 세션 확인 동안 화면을 덮는 회색 막 "Loading"(shared/ui/loading/Loading.tsx:3-7 · app/router/Router.tsx:78-81). 사진 올리는 중 자리는 다크에서도 밝은 그라디언트이고 전역 @keyframes shimmer 를 덮어쓴다(features/user-profile/ui/UserEditDialog.tsx:236-255).
  • 앱 적용 때 화면마다 정할 자리 — 어느 쿼리가 화면을 통째로 기다리는지(웹 페이지 스켈레톤 · 첫 번만 3화면), 지연 로딩 페이지 이동 중 표시, 홈 히어로의 흰 정지 막대(앱 dashboard/dashboard_screen.dart:713-726).

2026-10-04 — 그림 자리의 모서리를 Image Frame 대로

사용자가 이미지 비교 페이지 2A 에서 정했다 — 그림 모서리는 SEED 폭 기준(24 이하 4 · 48 이하 6 · 그 위 8 · 화면 폭 0)이고, 불러오는 동안의 스켈레톤도 다 받은 그림과 같은 모서리다. 그래서 "카드 · 썸네일 16" 을 둘로 나눴다 — 썸네일 · 카드 그림 같은 그림 자리는 Image Frame 의 모서리(radius 에 "4" · "6" 을 더하고 "8" 이 49 이상의 그림도 맡는다), "16" 은 카드 면(Card 모양 자리 전체)만이다. 그림 자리의 모서리는 레시피의 imageFrameRadius(폭) 이 고른다 — 손으로 고르지 않는다. 레시피 Skeleton 의 radius 에 "4" · "6" 이 더해진다. Image Frame 은 제 스켈레톤을 그 틀 안에 그리므로, 이 값을 쓰는 것은 줄이 통째로 기다릴 때처럼 틀 밖에서 그림 자리를 그릴 때다. 물건 타일(Logo Tile)은 이름을 알면 스켈레톤 없이 첫 글자부터 그린다(이미지 비교 6B).

이 페이지