suta · git:20261106.f934b66 · 2026-11-06 · sha256 88dbb93e4ff2c82c
suta git:20261106.f934b66A
Immutable. This exact content never changes and is served at /api/v1/blob/88dbb93e4ff2c82c.
---
name: suta
description: 웹·React Native 앱의 UI를 만들거나 고칠 때 AI 슬롭(AI slop) — 모든 것을 감싼 카드와 제각각인 여백, 기본 파랑·연노랑을 뿌린 탁한 색, 아무 데나 붙인 transition, 레이아웃을 흔드는 애니메이션, 동작 줄이기 설정 무시, 키보드로 못 쓰는 컴포넌트처럼 AI가 만든 티가 나는 UI — 을 없앤다. 이를 막는 레이아웃·색·글꼴·문구·모션 기준과 검증된 UI 패턴 57종의 코드(모달·바텀시트·토스트·드롭다운·캐러셀·탭·폼·제스처)를 제공한다. "로그인·목록·상세·설정·채팅 화면 만들어줘, 페이지 구현, 컴포넌트 추가, 레이아웃·여백 다듬어줘, 버튼 애니메이션 넣어줘, 문구 다듬어줘, 모바일 제스처, AI 티 안 나게" 요청과 UI, landing page, screen, layout, spacing, component, modal, dropdown, toast, animation, keyboard accessibility 작업에 쓴다. 화면이 없는 백엔드·데이터·문서 작업에는 쓰지 않는다.
---
# suta — AI 슬롭 없는 UI
AI에게 UI를 맡기면 어디서 본 듯한 결과가 나온다. 모든 구역을 카드로 감싸고, 프레임워크 기본 파랑을 쓰고, `div`에 `onClick`을 달아 키보드로 쓸 수 없게 만들고, `height`를 움직여 화면을 흔들고, 좁은 화면에서 가로로 넘친다. 이 스킬은 세 가지로 그것을 막는다.
- **기준** — 키보드·포커스·넘침·동작 줄이기 같은 사용 문제의 선(이 문서), 디자인 기준이 없는 프로젝트에서 쓰는 레이아웃·색·글꼴·문구 기본값(`references/defaults.md`).
- **패턴 코드 57종** — 모달·바텀시트·토스트·드롭다운·폼·제스처. 프레임워크와 무관한 코어(.ts, 의존성 0) + React 래퍼 + CSS를 프로젝트에 복사해 맞춘다.
- **검사** — `scripts/audit.mjs`가 소스에서 레이아웃·모션 문제를 file:line으로 찾는다. 플러그인으로 설치하면 파일을 고칠 때마다 돈다.
수타(手打)는 기계가 아니라 손으로 친다는 뜻이다.
## 우선순위
판단이 부딪히면 위에 있는 것을 따른다.
1. **사용자가 명시한 요구.** 요청한 화면·기능·상태(오류·빈 상태·로딩·비활성 등)를 하나도 빼지 않는다. 요청하지 않은 기능·구역·설정 행·데이터 항목은 더하지 않는다. 더하면 좋을 것은 보고에 제안으로 적는다. 요청한 것을 보이는 방식과 화면의 틀은 더하는 것이 아니다(아래 "요청 범위와 사실 정보").
2. **프로젝트의 기존 디자인 체계.** 토큰(색·간격·글자·반경)·컴포넌트·브랜드색·글꼴·이미 있는 화면의 구조를 그대로 쓴다. 카드를 쓸지, 색을 몇 개 쓸지, 설정 목록을 어떤 모양으로 둘지, 강조색이 얼마나 맑은지, 어떤 말투인지는 취향이다. suta 기본값으로 바꾸지 않는다.
3. **실제 사용 문제.** 아래 "사용 문제" 표에 해당하면 기존 스타일이라도 고친다. 무엇이 문제인지(측정값)와 어디까지 바꿨는지를 보고하고, 그 범위 밖은 건드리지 않는다. 예: 브랜드 회색 글자가 흰 면에서 3.9:1이면 그 글자 색 하나만 4.5:1이 되게 어둡게 하고 팔레트의 나머지는 그대로 둔다. 명시한 요구가 사용 문제를 만들면 요구를 따르고, 문제와 대안을 보고에 적는다.
4. **suta 기본값.** 디자인 기준이 없는 프로젝트(새 프로젝트, 토큰·컴포넌트·브랜드 가이드가 없는 화면)에서만 `references/defaults.md`의 값을 쓴다. 사용자가 "AI 티를 없애 달라"고 하면 요청한 화면에 적용하되, 브랜드색·글꼴·컴포넌트는 유지하고 바꾸고 싶은 것은 제안한다.
### 사용 문제와 취향
| 사용 문제 — 기존 디자인이어도 고친다 | 취향·관례 — 기준이 없을 때만 suta 기본값 |
|---|---|
| 키보드로 닿지 않는 컨트롤, 보이지 않는 포커스, 열고 닫을 때 포커스 이동·복귀·Esc가 없는 모달·시트·메뉴 | 카드·상자를 쓸지, 몇 겹인지 |
| 글자 대비 4.5:1 미만, 12px 미만 글자, 작은 누르는 영역(모바일 44px, 최소 24px) | 강조색과 그 맑기, 유채색 수, 옅은 색 면, 무채색의 온도 |
| 320·390px 폭의 가로 넘침, 어느 폭에서든 칸 밖으로 잘리는 글자·숫자, 좁은 화면에서 사라지는 표의 열, 내용을 가리는 하단 고정 바, 모바일에서 아래가 잘리는 `100vh`, 확대되는 16px 미만 입력칸 | 글꼴, 말투(해요체·합쇼체), 버튼 문구 길이 |
| 동작 줄이기 설정 무시, 레이아웃 속성(`height`·`top`)을 움직이는 애니메이션, `transition: all`, 장식용 무한 반복 | 설정·랜딩·홈의 구성(폭 가득 목록과 둥근 묶음, 구역 수, 번호 목록) |
| 스크린 리더에 전달되지 않는 상태 변화, 보이는 글자와 다른 접근 이름, 색만으로 전하는 정보 | 간격 척도, 글자 크기·굵기·반경의 종류 수 |
| 확대 막기(`user-scalable=no`), 호버로만 열리는 기능, 연타로 두 번 제출되는 버튼, 늘어난 사진 | 아이콘 모양, 이모지 아이콘, 그라데이션·그림자, 시간·이징 값 |
검사(`scripts/audit.mjs`)도 이 선으로 나뉜다.
- **error는 모두 사용 문제다.** 11px 미만 글자, 그림(`role="img"`) 안의 버튼, 넘치는 grid 틀, 스크롤 칸 밖으로 새는 숨김 글자, 레이아웃 애니메이션, `transition: all`, 동작 줄이기 누락 등이다. 기존 코드에서도 고친다.
- **warn 중 사용 문제**는 레이아웃 검사의 `tiny-text`(11px대)·`accent-contrast`·`viewport-height`·`input-zoom`·`zoom-disabled`·`image-distort`와 모션 검사의 `infinite-loop`(장식용 반복일 때)·`js-interval-anim`·`will-change-global`·`no-reduced-motion`(React Native)이다. 기존 체계에서도 고친다.
- **나머지 warn은 취향·관례다**(`nested-card`·`hue-count`·`tinted-surface`·`muddy-accent`·`spacing-off-scale`·`emoji-icon`·`numbered-markers`·`duration-range`·`ease-in-exit` 등). 디자인 기준이 없는 화면에서만 고친다. 기존 체계의 값에서 난 것은 그대로 두고 보고에 적는다.
- `meta-copy`(화면에 남은 개발·데모 문구)는 아래 "사실 정보"를 따른다.
## 요청 범위와 사실 정보
- **요청을 목록으로 적고 끝에 대조한다.** 요청한 상태(오류·빈 상태·로딩 등)는 실제 조작으로 그 상태가 되어야 하고, 확인할 수 있어야 한다. 코드에만 있고 그 상태가 될 길이 없으면 빠진 것이다. 요청이 상태를 "보여 달라"고 하면(퍼블리싱·시안) 상태별 화면을 한 벌 더 두거나 주소 매개변수(`?state=error`)로 열리게 한다. 제품 화면 안에 상태 미리보기 버튼은 두지 않는다.
- **화면의 틀은 더하는 요소가 아니다.** 하위 화면(글쓰기·상세·목록의 다음 단계)을 나가는 길(뒤로·닫기)과 화면 제목, 웹 페이지의 사이트 머리(로고 + 주 메뉴)는 요청에 없어도 둔다.
- 주 메뉴 이름은 프로젝트에 있으면 그것을 쓴다. 없으면 서비스 종류에서 흔한 구역 이름 3~5개를 화면을 보이기 위한 콘텐츠로 두고(지금 화면이 속한 메뉴를 표시), 보고에 바꿀 값으로 적는다. 메뉴는 링크 자리일 뿐이다 — 그 메뉴가 가리킬 새 화면·기능은 만들지 않는다.
- 메뉴 항목을 하나만 두면 덜 만든 화면으로 읽힌다(여행 기사 재측정에서 suta 세 번 모두 메뉴가 하나뿐이었고, 진 판정 12번 중 7번이 사이트 메뉴를 짚었다).
- **요청한 것을 보이는 방식은 더하기가 아니다.** 아래 왼쪽은 요청 범위 안이라 관례대로 갖추고, 오른쪽은 요청에 없으면 더하지 않고 보고에 제안한다.
| 요청 범위 안 — 요청한 것을 보이는 방식·화면의 틀 | 제안 — 요청에 없는 기능·구역·데이터 |
|---|---|
| 요청한 평점을 숫자 + 막대·별로, 요청한 상태를 배지로 | 찜·좋아요·공유·구독·장바구니·검색 같은 기능 |
| 요청한 필수 항목의 필수 표시, 버튼 위 남은 항목 안내 | 후기·고객사 로고 띠·배너·추천 목록 같은 구역 |
| 요청한 본문의 소제목 번호·인용 처리·드롭캡·사진 설명 | 발매 정보 표·등급·통계처럼 요청에 없는 데이터 항목 |
| 요청한 고르는 항목을 칩·세그먼트·기본값 스테퍼·시트로 | 설정의 고객센터·약관·버전처럼 요청에 없는 행 |
| 뒤로·닫기, 화면 제목, 사이트 머리와 메뉴 이름, 경로 표시 | 메뉴가 가리킬 새 화면, BEST·NEW 같은 마케팅 배지 |
- 블라인드 판정은 오른쪽 요소(찜·배지·검색·공유)가 있는 화면을 자주 골랐다(상품 필터·모임 만들기·기사). 그래도 더하지 않는다 — 사용자가 정한 규칙이다. 판정이 고를 만한 것은 제안 목록의 앞에 적는다.
- **관례는 배치에 쓰고, 더하는 근거로 쓰지 않는다.** 화면 유형의 관례에 흔한 요소(설정의 고객센터·약관·버전, 랜딩의 후기·로고 띠, 홈의 검색창)라도 요청에 없으면 더하지 않고 제안한다. 패턴이 있다고 요청에 없는 기능(담기 애니메이션·좋아요)을 더하지 않는다.
- **사실 정보는 지어내지 않는다.** 고객사 로고·사용자 수·후기·성과 수치·수상·인증처럼 서비스의 실적을 말하는 정보다.
| | 실제 서비스(기본으로 이렇게 본다) | 명시적 가상 목업 |
|---|---|---|
| 판단 | 사용자가 시안·목업·데모·포트폴리오라고 하지 않았다 | 사용자가 그렇게 말했거나, 작업 자체가 지어낸 서비스를 보이는 데모다 |
| 고객사 로고·사용자 수·후기·성과 수치·수상 | 사용자가 준 자료만 쓴다. 없으면 그 구역을 만들지 않고, 보고에 필요한 자료를 요청한다 | 지어낼 수 있다. 실존 회사·사람의 이름과 로고는 쓰지 않는다. 그 구역 안에 "예시"임을 화면에 보이게 적는다(README·주석에만 두지 않는다) |
| 요청한 구역의 값(요금·사양·연락처) | 받은 값을 쓴다. 받지 못했으면 값을 데이터 한 곳에 모으고 `확인 필요` 주석을 달고, 보고에 바꿔야 할 값을 나열한다 | 지어낼 수 있다. 화면에 예시 표시를 둔다 |
| 화면을 보이기 위한 콘텐츠(음식 메뉴·상품·사람 이름, 사이트 메뉴 이름, 일정, 제품 화면 그림 속 데이터) | 지어낼 수 있다. 데이터는 한 곳에 모아 실제 데이터로 바꾸기 쉽게 두고, 화면의 숫자끼리 맞게 그 데이터에서 계산한다(지표·차트·표의 합계가 같고, 처리 건수가 접수 건수보다 많지 않다) | 같다 |
| 실제 개인정보(대화·git 설정에 보이는 이름·이메일·전화번호), 개발용 계정·비밀번호 | 화면에 두지 않는다 | 화면에 두지 않는다 |
## 작업 절차
1. **요청을 목록으로 적는다.** 화면·기능·상태·문구를 빠짐없이 적는다.
2. **프로젝트를 읽는다.** 프레임워크(React·Vue·Svelte·순수 JS·React Native), TypeScript 여부, 스타일 방식(CSS·CSS Modules·Tailwind·StyleSheet)을 확인한다. 디자인 기준(토큰·컴포넌트·브랜드 가이드·일관된 기존 화면)이 있는지 판단하고, 있으면 그 값과 구조를 출처로 삼는다.
3. **읽을 문서를 고른다**(아래 표).
4. **구조를 정한다.** 요청한 요소에 순위를 매기고 1순위 하나를 정한다. 기준이 없는 새 화면이면 화면 유형의 뼈대에서 시작한다.
5. **패턴을 고른다.** 아래 색인과 `references/catalog.md`에서 요청의 컴포넌트·상호작용에 맞는 패턴을 모두 고르고, 고른 패턴의 `PATTERN.md`를 끝까지 읽는다. 주의사항에 접근성·성능 함정이 있다. 맞는 패턴이 없으면 이 문서의 선만으로 구현한다.
6. **코드를 옮긴다.** PATTERN.md 파일 표의 "복사 대상"을 따른다.
- 첫 줄이 `@shared-core`인 파일은 그 헤더를 지우지 않는다. 같은 코드가 다른 패턴에도 들어 있다는 표시다.
- JavaScript 프로젝트면 타입 표기만 벗긴다. 로직은 그대로다.
- React가 아니면 코어(.ts)와 CSS만 쓰고, 상태 연결은 그 프레임워크의 방식으로 한다.
- 다른 패턴의 코드가 필요하면 import 경로로 엮지 말고 그 파일을 함께 복사한다.
- 패턴의 공개 변수(`--sheet-duration` 같은)는 프로젝트 토큰에 잇는다. 프로젝트에 기준이 없으면 `patterns/layout-principles/assets/layout-tokens.css`와 `patterns/motion-principles/assets/motion-tokens.css`를 함께 넣는다.
7. **검사한다.** `node <이 스킬 폴더>/scripts/audit.mjs <바꾼 파일·폴더>`를 실행해 error가 0이 될 때까지 고친다. warn은 위 우선순위대로 판단한다. Node 22.18 이상이 필요하다. 실행할 수 없으면 `patterns/layout-audit/PATTERN.md`·`patterns/motion-audit/PATTERN.md`의 규칙표로 직접 확인한다.
- 플러그인으로 설치했으면 파일 편집 도구(Write·Edit)로 고칠 때마다 같은 검사가 돌고, 방금 바꾼 줄의 error만 돌려받는다. 파일에 원래 있던 문제로는 멈추지 않는다.
- 셸(`cat > 파일 <<EOF`)로 쓴 파일에는 자동 검사가 돌지 않는다. 화면 파일은 편집 도구로 쓰고, 끝에 통합 검사를 직접 돌린다.
- 검사는 소스 텍스트만 읽는다. error 0이어도 요청 누락·겹친 정보·실제 동작은 따로 확인한다.
8. **화면에서 확인한다.** 브라우저나 기기를 쓸 수 있으면 키보드만으로 조작, 열고 닫을 때의 포커스, 320·390px 폭의 넘침, 동작 줄이기를 직접 본다. 쓸 수 없으면 확인하지 못했다고 보고한다.
9. **보고한다.** 쓴 패턴과 이유 1~2줄, 고친 사용 문제와 범위, 기존 체계라 그대로 둔 것, 요청에 없어 더하지 않은 제안, 받아야 할 자료(사실 정보), 확인하지 못한 것을 적는다.
## 읽을 문서
경로는 이 스킬 폴더 기준이다. 맞는 줄을 모두 읽는다.
| 이럴 때 | 읽는다 |
|---|---|
| 모든 UI 작업 | 이 문서 전체 — 우선순위, 사용 문제, 사실 정보, 늘 지키는 선, 끝내기 전에 |
| 프로젝트에 디자인 기준이 없다 · 새 화면의 여백·색·글꼴·문구를 정한다 · "AI 티 안 나게", "밋밋해", "색이 많아" | `references/defaults.md` — AI가 흔히 만드는 틀과 기본 대안, 레이아웃·모션 값 |
| 기존 화면을 다듬는다 | `references/defaults.md`의 1절 "덜어내기"(요청한 범위 안에서) |
| 유형이 있는 화면 — 홈·목록·상세·검색·필터·채팅방·알림·프로필·폼·글쓰기·설정·장바구니·대시보드·리포트·기사·랜딩·AI 채팅·쇼핑·은행·증권·게이미피케이션·시작 안내 | `patterns/layout-principles/references/screen-conventions.md`의 그 유형 절과 "모든 화면에 공통". 앱이면 "하단 탭 바(GNB)"·"콘텐츠 그림", 웹뷰면 "모바일 웹뷰·웹앱"도 |
| 랜딩·서비스 소개 | 위 문서의 "랜딩" 절과 이 문서의 "사실 정보" |
| 색·글꼴 | `patterns/layout-principles/references/tone.md` |
| 화면 문구 | `patterns/layout-principles/references/copy.md` |
| React Native·Expo | `patterns/layout-principles/references/react-native.md`부터 — 웹 패턴 코드를 붙이지 않고 값·동작·접근성을 네이티브로 옮긴다 |
| 컴포넌트·상호작용을 만든다 | `references/catalog.md` → 고른 패턴의 `PATTERN.md` |
| 애니메이션 시간·이징 | `references/defaults.md`의 6절 "모션 값", 토큰은 `patterns/motion-principles/PATTERN.md` |
| 끝내기 전 | 이 문서의 "끝내기 전에" + `references/checklist.md`의 해당 절 |
## 늘 지키는 선 — 사용 문제
기존 디자인 체계와 상관없이 지킨다. 맞는 패턴이 없는 UI에도 적용된다.
### 접근성
| AI가 흔히 하는 것 | 대신 | 근거 패턴 |
|---|---|---|
| `div`에 `onClick` | `button`·`a`·`input`·`dialog` 같은 기본 요소 — 키보드와 스크린 리더가 그냥 된다 | `switch`, `checkbox-radio`, `modal-dialog` |
| 포커스 표시를 지움 | `:focus-visible`로 키보드 사용자에게만 보인다 | `dropdown-menu`, `quantity-stepper` |
| 모달·시트를 열어도 포커스·Esc·스크롤이 그대로 | 열면 포커스를 옮기고, Esc로 닫고, 배경을 `inert`로 막고, 닫으면 원래 자리로 포커스를 돌려준다 | `modal-dialog`, `bottom-sheet` |
| 알림·로딩 결과를 눈으로만 알림 | `role="status"`·`aria-live`로 읽어 준다 | `toast-stack`, `infinite-scroll` |
| 매초 바뀌는 값(걸음 수·타이머)이나 카운트업을 `role="status"`·`aria-live` 안에 둔다 — 바뀔 때마다 읽힌다 | 낭독 영역에는 사건(받았어요·목표 달성)만. 카운트업은 굴러가는 숫자를 숨기고 최종값만 읽힌다 | `count-up`, `celebration` |
| 버튼·행의 이름을 보이는 글자와 다른 문장의 `aria-label`로 덮는다(호가 행·목록 행) | 이름은 보이는 글자로 시작한다. 덧붙일 설명은 뒤에 붙이거나 `aria-describedby`로(WCAG 2.5.3) | `layout-principles` |
| 칸 이름이나 필수 표시를 안내 문구(placeholder) 안에만 둔다 — 입력을 시작하면 사라진다 | 칸 이름은 보이는 라벨이나 늘 보이는 제목 자리에, 필수는 라벨 옆이나 버튼 위 한 줄에 둔다. 안내 문구는 예시만. 예외: 제목·본문뿐인 글쓰기는 큰 제목 줄과 본문 영역의 자리가 이름을 대신한다 — `aria-label`로 이름을 주고, 필수는 버튼 위 한 줄로 늘 보인다 | `floating-label`, `layout-principles` |
| 마우스 호버로만 열리는 기능 | 키보드 포커스와 터치로도 열린다(길게 누르기·포커스) | `tooltip`, `long-press-menu` |
| 입력칸 확대를 막으려고 viewport에 `user-scalable=no`·`maximum-scale=1`을 넣는다 | 확대는 막지 않는다. 입력칸 글자를 16px 이상으로 하면 iOS가 확대하지 않는다 | `layout-audit` |
### 조작·피드백
| AI가 흔히 하는 것 | 대신 | 근거 패턴 |
|---|---|---|
| 눌러도 아무 반응이 없음 | 누르는 순간 눌린 상태가 보인다 — 기존 체계의 눌림 스타일, 없으면 살짝 줄었다 돌아온다 | `press-feedback` |
| 로딩은 스피너 하나, 연타하면 두 번 제출 | 자리를 잡는 스켈레톤, 진행 중에는 버튼을 잠근다 | `skeleton`, `loading-button` |
| 끌어서 닫기가 손가락을 따라오지 않음 | 드래그 중에는 손가락을 따라가고, 거리·속도로 닫을지 판정한다 | `bottom-sheet`, `swipe-dismiss-viewer` |
| 늦게 온 검색 응답이 최신 결과를 덮음 | 입력이 멈춘 뒤 한 번만 요청하고, 옛 응답은 버린다 | `search-suggest` |
| 무한 스크롤의 중복 호출·연쇄 로딩 | 미리 불러오되 중복·연쇄·실패 폭주를 막고, "더 보기" 버튼을 함께 둔다 | `infinite-scroll` |
| 제스처와 페이지 스크롤이 충돌 | `touch-action`으로 제스처 방향을 지정한다 | `bottom-sheet`, `swipe-to-delete` |
| 화면을 옮겨도 앞 화면의 토스트가 남는다. 토스트에 "보러 가기" 같은 버튼을 단다 | 화면이 바뀌면 토스트를 비운다(`clearToasts`). 토스트는 알림만 — 장바구니로 가는 길은 머리의 장바구니 아이콘(개수 배지가 바뀐다)이 맡는다 | `toast-stack` |
| 키보드가 올라오면 하단 탭 바가 따라 올라와 입력칸·제안 목록을 가린다 | 입력칸에 초점이 있는 동안 탭 바를 숨긴다 | `bottom-nav` |
### 넘침·크기
| AI가 흔히 하는 것 | 대신 | 근거 패턴 |
|---|---|---|
| 좁은 화면에서 열을 유지하고 글자를 9~11px로 줄인다 | 열을 줄인다. 글자 12px 이상, 누르는 영역 44px(최소 24px) | `layout-audit` |
| 짧은 영어 더미로만 확인한다 | 가장 긴 값·빈 값·320·390px 폭으로 확인한다 | `layout-principles` |
| 지표 칸을 같은 폭으로 나누고 큰 금액을 크게 둔다 — 가장 긴 값이 데스크톱에서도 칸 밖으로 잘린다 | 가장 긴 값으로 넓은 폭과 좁은 폭을 모두 본다. 금액은 단위를 줄이거나(7.9억 원) 칸에 `min-width: 0`을 두고 글자 크기를 칸에 맞춘다 | `layout-principles` |
| 좁은 화면에서 표의 열을 빼거나, 가로로 넘기는 표의 핵심 값 열(매출·금액)을 첫 화면 밖에 둔다 | 값은 빼지 않는다. 행마다 "이름 + 핵심 값 + 나머지 값 한 줄" 목록으로 바꾸거나, 이름·핵심 값 두 열을 고정하고 나머지를 넘긴다. 첫 화면 폭 안에 이름과 핵심 값이 함께 보여야 한다 | `layout-principles` |
| 화면 높이를 `100vh`로 둔다 | `100dvh` — 모바일 브라우저·웹뷰에서 아래가 잘리지 않는다 | `layout-audit` |
| 입력칸 글자를 16px 미만으로 둔다 | 16px 이상 — iOS가 초점을 받을 때 화면을 확대하지 않는다 | `layout-audit` |
| 화면 높이 grid 틀(행 둘 이상 + `fr`)에 열을 적지 않는다 | `grid-template-columns: minmax(0, 1fr)` — 입력칸 기본 폭·줄임표 글자가 틀을 화면보다 넓게 민다 | `layout-audit` |
| 하단 고정 바가 마지막 내용을 가린다 | 본문 아래 여백에 바 높이와 `env(safe-area-inset-bottom)`을 더한다 | `layout-principles` |
| 사진에 `object-fit: fill` | `aspect-ratio` + `object-fit: cover` — 늘이지 않고 잘라 채운다 | `layout-principles` |
| 한국어 문구가 단어 중간에서 줄바꿈된다 | `word-break: keep-all` | 한국어 조판 |
### 모션
| AI가 흔히 하는 것 | 대신 | 근거 패턴 |
|---|---|---|
| `transition: all` | 움직일 속성을 이름으로 나열한다 | `motion-audit` |
| `width`·`height`·`top`·`left`·`margin` 애니메이션 | `transform`·`opacity`만 움직인다. 높이는 `grid-template-rows: 0fr → 1fr`, 위치 변화는 FLIP | `accordion`, `flip-list` |
| 장식용 무한 반복 | 하루에 100번 보는 것은 움직이지 않는다. 모션은 "무슨 일이 일어났는지" 알릴 때만 쓴다 | `motion-principles` |
| `setInterval`로 스타일 갱신 | CSS transition, 필요하면 `requestAnimationFrame` | `motion-audit` |
| 상시 `will-change` | 움직이기 직전 상태에서만 | `motion-audit` |
| 동작 줄이기 설정을 무시한다 | 모든 CSS에 `@media (prefers-reduced-motion: reduce)` — 이동·확대를 끄고 짧은 페이드만 남긴다 | 모든 패턴 |
## 끝내기 전에
- [ ] 요청 목록의 화면·기능·상태가 모두 있고, 상태마다 화면에서 확인할 수 있다. 요청에 없는 기능·구역·항목은 더하지 않았다.
- [ ] 기존 디자인 체계의 토큰·컴포넌트·구조를 그대로 썼다. 바꾼 곳은 사용 문제 때문이고, 문제와 범위를 보고에 적었다.
- [ ] 실제 서비스 화면에 지어낸 고객사·사용자 수·후기·성과 수치가 없다. 가상 목업이면 그 구역에 예시 표시가 보인다. 실제 개인정보가 없다.
- [ ] 클릭할 수 있는 것은 모두 키보드로 닿고, 포커스가 보인다.
- [ ] 열리고 닫히는 것(모달·시트·메뉴)은 열면 포커스가 안으로 가고, Esc로 닫히고, 닫으면 포커스가 돌아온다.
- [ ] 상태 변화(저장됨·오류·로딩 끝)는 스크린 리더에도 전달되고, 매초 바뀌는 값은 낭독 영역 밖에 있다.
- [ ] 글자는 12px 이상·대비 4.5:1 이상이고, 누르는 영역은 44px(최소 24px)이다.
- [ ] 320·390px 폭에서 가로로 넘치거나 잘리는 요소가 없고, 넓은 폭에서도 가장 긴 값이 칸 안에 있으며, 하단 고정 바가 내용을 가리지 않는다.
- [ ] 움직이는 속성이 `transform`·`opacity`뿐이고(예외는 근거를 주석으로 남겼다), 새로 쓴 CSS마다 reduced-motion 블록이 있다.
- [ ] 통합 검사(`scripts/audit.mjs`) error가 0이고, 위 "사용 문제" warn이 없다.
- [ ] 브라우저에서 확인한 것과 확인하지 못한 것을 나눠 보고했다.
- [ ] 디자인 기준이 없는 화면이거나 유형이 있는 화면이면 `references/checklist.md`의 해당 절을 확인했다.
## 패턴 색인
이름과 데모 제목만 있다. 한 줄 설명과 고르는 예는 `references/catalog.md`에 있고, 고른 패턴은 `patterns/{패턴}/PATTERN.md`를 끝까지 읽는다.
<!-- index:start -->
| 묶음 | 패턴 |
|---|---|
| 등장과 전환 | `enter-exit` 진입/퇴장 애니메이션, `scroll-reveal` 스크롤 리빌, `sticky-header` 스티키 헤더 전환, `flip-list` 리스트 재배치 (FLIP), `zoom-lightbox` 확대 전환 라이트박스, `modal-dialog` 모달 다이얼로그, `spring-physics` 스프링 물리 모션, `theme-toggle` 다크모드 전환, `card-expand` 카드 확장, `card-stack` 카드 묶음, `stretchy-header` 늘어나는 이미지 헤더 |
| 로딩과 진행 | `skeleton` 스켈레톤 시머, `count-up` 숫자 카운트업, `story-progress` 스토리 프로그레스, `loading-button` 로딩 버튼, `infinite-scroll` 무한 스크롤, `virtual-list` 가상 스크롤, `progress-ring` 원형 진행 링 |
| 피드백 | `press-feedback` 프레스 피드백, `toast-stack` 토스트 스택, `like-pop` 좋아요 팝 + 더블탭 버스트, `celebration` 축하 오버레이, `cart-fly` 카트 플라이, `tooltip` 툴팁, `form-shake-error` 폼 에러 흔들림 |
| 내비게이션 | `tab-indicator` 탭 인디케이터 슬라이드, `carousel` 스냅 캐러셀, `hamburger-menu` 햄버거 메뉴, `page-transition` 화면 전환, `dropdown-menu` 드롭다운 메뉴, `glass-nav` 스크롤 반응 유리 GNB, `bottom-nav` 하단 탭 바(GNB) |
| 제스처 | `bottom-sheet` 바텀시트, `pull-to-refresh` 당겨서 새로고침, `swipe-to-delete` 밀어서 삭제, `pinch-zoom` 피드 핀치줌, `swipe-dismiss-viewer` 끌어내려 닫는 뷰어, `drag-to-reorder` 끌어서 순서 바꾸기, `long-press-menu` 길게 눌러 메뉴, `edge-swipe-back` 가장자리 스와이프 뒤로가기 |
| 컨트롤 | `select` 커스텀 셀렉트, `accordion` 아코디언, `switch` 토글 스위치, `floating-label` 플로팅 라벨 입력, `checkbox-radio` 체크박스 · 라디오, `otp-input` 인증번호 입력, `search-suggest` 검색어 자동완성, `range-slider` 범위 슬라이더, `quantity-stepper` 수량 스테퍼, `file-upload` 파일 업로드, `segmented-control` 세그먼트 컨트롤, `wheel-picker` 휠 피커 |
| 표면과 스타일 | `glass-surface` 유리판 (글라스모피즘) |
| 원칙과 검토 | `motion-principles` 모션 원칙과 토큰, `motion-audit` 모션 검사, `layout-principles` 레이아웃 원칙과 토큰, `layout-audit` 레이아웃 검사 |
<!-- index:end -->
## 쓰지 않는 경우·한계
- 화면이 없는 작업(API·데이터·문서·배포)에는 쓰지 않는다.
- 이 스킬은 레이아웃·글자·색·글꼴·문구·모션·상호작용·접근성을 다룬다. 프로젝트에 디자인 체계가 있으면 그 값과 구조를 쓰고, 이 스킬에서는 사용 문제의 선과 패턴 코드만 가져간다. 체계가 없을 때만 이 스킬의 토큰과 기본값을 쓴다.
- 기본값의 수치는 국내 앱 31곳·웹사이트 19곳·SaaS 랜딩 42곳에서 쟀다. 다른 나라 서비스의 관례와는 다를 수 있다.
- 패턴 코드는 React와 순수 TypeScript로 쓰여 있다. 다른 프레임워크는 코어와 CSS를 가져가 연결 부분만 새로 쓴다. React Native에는 코드가 없다 — `react-native.md`의 대응표로 옮긴다.
- 문구 검사는 코드 속 한국어 문자열의 끝맺음과 개발용 낱말만 본다. 40자 넘는 설명 문단과 약관 문장은 세지 않고, 용어가 맞는지·문장이 쉬운지는 사람이 읽는다.
- 검사는 소스 텍스트를 읽는 정적 검사다. 글자 대비·누르는 영역·좁은 폭 넘침과 실제 동작은 브라우저에서 확인한다. React Native는 `StyleSheet.create`의 값과 Reanimated 애니메이션까지 읽고, 나머지는 기기에서 확인한다.