기다리게 하면 무엇을 기다리는지, 비어 있으면 무엇이 없는지 말한다. 사용자가 채울 수 있으면 다음 행동을 한 줄 덧붙인다.
- 버튼을 눌러 서버 응답을 기다릴 때(저장·추가·다시 시도)
- 목록·모달·화면 영역을 처음 불러올 때
- 불러왔는데 항목이 하나도 없을 때(채팅·멤버·검색 결과)
무엇을 기다리나?
├─ 내가 누른 버튼의 처리 ─────────── Button isLoading — 라벨은 그대로, 스피너만
├─ 영역(목록·모달·패널)의 첫 로딩 ── StateMessage kind="loading" — 스피너 + "~를 불러오는 중…"
├─ 화면 전체의 첫 로딩 ──────────── Spinner lg brand + 글자 (프로젝트 화면·로그인 직후)
└─ AI 답변 ─────────────────────── LoadingDots / ChatComposer status (별도, 이 패턴 밖)
불러왔는데 비어 있나?
├─ 사용자가 채울 수 있음 ─────────── StateMessage kind="empty" + action — 다음 행동 한 줄
└─ 할 수 있는 게 없음 ───────────── StateMessage kind="empty" — 무엇이 없는지만
불러오다 실패했나? ──────────────── 에러 패턴(error.md) — InlineAlert + 다시 시도
- 라벨을 "추가 중..."으로 바꾸지 않는다. 버튼 폭이 흔들리고, 스피너와 뜻이 겹친다.
disabled로 막고 끝내지 않는다 — 막힌 이유(처리 중)가 안 보인다.isLoading이 막기 + 스피너 +aria-busy를 함께 한다.- 적용: 섹션·폴더 추가/저장, 초대 링크 다시 만들기, 프로젝트·프로젝트 목록 다시 시도
role="status"로 스크린리더에 알린다. 모양은text-caption·text-fg-muted+ Spinner sm.- 가운데 정렬 목록(모달 안 리스트)은
align="center". - 적용: 섹션 목록, 프로젝트 멤버, 팀 채팅 참여자·방장 넘기기 후보, 멤버 선택 목록(
ProjectMemberPickList loadingMessage), 저장소 목록, 다이제스트 대상·원본, 초대 정보, 메시지 목록
- 무엇이 없는지 먼저, 다음 행동은 한 줄 아래 흐리게.
- 다음 행동은 그 자리에 실제로 있는 컨트롤을 가리킨다. 버튼이 없으면(권한·기능 꺼짐) 안내도 없다.
- 적용: 내 채팅·팀 채팅 목록(다음 행동 있음), 프로젝트 목록, 멤버 선택·참여자·방장 후보·공유된 대화(무엇이 없는지만)
- sm 12 문장 안 · md 14 버튼 · lg 32 화면.
tone="brand"는 화면 로딩(회색 원 + 오렌지 호), 나머지는 글자색을 따른다. - 혼자 두지 않는다. 항상 무엇을 기다리는지 글자와 함께.
- 로딩: "~를 불러오는 중…" — 대상을 넣는다. "로딩 중" 같은 대상 없는 문구 금지.
- 빈 상태: "~가 없어요." + 다음 행동 "+ 를 눌러 ~해 보세요."
- 어미·말줄임표·용어는
docs/copy.md를 따른다(해요체,…).
| Do | Don't |
|---|---|
스피너 + 추가 |
라벨을 추가 중... 으로 바꾸기 |
isLoading 으로 막기 |
disabled 만 걸고 처리 중 표시 없음 |
| 빈 채팅 목록에 "+ 를 눌러 코드에 질문해 보세요." | + 버튼이 없는데 "+ 를 눌러" 안내 |
로딩을 loadingMessage 로 따로 |
로딩을 emptyMessage 문자열로 흉내내기 |
- 2026-09-26: 영역 로딩이 곳마다 크기·색·정렬이 달랐고(text-label/caption, fg-subtle/muted) 스피너가 없던 것을
StateMessage로 통일. 손으로 만든 스피너 6곳을Spinner로, 버튼 라벨 바꿔치기 4곳과 raw 다시 시도 버튼 2곳을Button isLoading으로 정리. 빈 채팅 목록에 다음 행동 한 줄 추가(팀 채팅은 작성 가능할 때만). 문구 어미는 Phase F 로 미룸.