토큰 · 컴포넌트 · 운영 룰 통합 명세.
가이드 패턴 ↔ 코드 파일 색인. 변경 시 단일 진입점.
가이드 패턴 ↔ 두 코드베이스 (모바일 RN · 어드민 Next) 컴포넌트 파일 색인.
모바일은 React Native + StyleSheet, 어드민은 Next.js + Tailwind. 1:1 미러 불가 — 상태 컬럼으로 일치도 표시.
| 가이드 패턴 | 모바일 (RN) | 어드민 (Next) | 상태 |
|---|---|---|---|
| Button (9.2) | 인라인 TouchableOpacity | components/ui/Button.tsx | ⚠ 모바일 추출 필요 |
| Input (9.4 · 10.6) | src/components/FormFields.tsx::InputField | components/ui/Input.tsx | ✓ 양쪽 |
| Textarea (9.4) | FormFields::InputField multiline | components/ui/Textarea.tsx | ✓ 양쪽 |
| Select (9.4) | FormFields::OptionGroup (옵션 그룹 UX) | components/ui/Select.tsx | △ UX 차이 |
| Checkbox (9.4) | FormFields::OptionGroup (칩 그룹) | components/ui/Checkbox.tsx | △ UX 차이 (모바일 칩 패러다임) |
| FormField (10.6) | FormFields::InputField (label 내장) | components/ui/FormField.tsx | ✓ 양쪽 |
| FormSection (10.6) | FormFields::StepCard | components/ui/FormSection.tsx | ✓ 양쪽 |
| Card (10.2) | PostCard.tsx + sectionCard 인라인 | components/ui/Card.tsx | ⚠ 모바일 추출 필요 |
| Banner (10.3) | src/components/AlertBanner.tsx (count + cta) | components/ui/Banner.tsx | △ 인터페이스 차이 |
| AlertDialog (11.1) | AssignmentModals.tsx (도메인) | components/ui/AlertDialog.tsx | ⚠ 모바일 일반화 안 됨 |
| ActionSheet (11.4) | src/components/ActionSheet.tsx | — | 🟢 모바일 전용 (RN Alert.alert 대체) |
| 가이드 패턴 | 모바일 (RN) | 어드민 (Next) | 상태 |
|---|---|---|---|
| PageContainer (10.1) | — | components/ui/PageContainer.tsx | 🔵 어드민 전용 (RN Screen 과 별) |
| PageHeader | src/components/PageHeader.tsx | components/ui/PageHeader.tsx | ✓ 양쪽 |
| CardHeader (10.2) | — | components/ui/CardHeader.tsx | 🔵 어드민 전용 |
| SectionHeader | src/components/SectionHeader.tsx | 인라인 h2/h3 | ⚠ 어드민 추출 필요 |
| EmptyState | src/components/EmptyState.tsx | 인라인 div | ⚠ 어드민 추출 필요 |
| Greeting | src/components/Greeting.tsx | — | 🟢 모바일 전용 |
| DLRow (10.4) | — | components/ui/DLRow.tsx | 🔵 어드민 전용 |
| FilterToolbar (10.8) | RN 화면 안 인라인 | components/ui/FilterToolbar.tsx | 🔵 어드민 전용 (B 패턴 — 칩 누적 + popover + 검색) |
| DataTable | — | components/ui/DataTable.tsx | 🔵 어드민 전용 (RN 은 FlatList 사용) |
| Pagination (10.8) | RN ScrollView 무한 스크롤 | components/Pagination.tsx · components/ClientPagination.tsx | 🌐 플랫폼 특수 (URL/client 변형 둘) |
| 가이드 패턴 | 모바일 (RN) | 어드민 (Next) | 상태 |
|---|---|---|---|
| StatusBadge (8) | src/components/StatusDot.tsx (점) | components/StatusBadge.tsx (라벨) | △ UX 차이 |
| FilterChip (8) | src/components/FilterChip.tsx | deprecated — FilterToolbar 가 흡수 | 🟢 모바일 전용 (어드민은 삭제됨) |
| SegmentedControl | — | components/ui/SegmentedControl.tsx | 🔵 어드민 전용 (월간/주간/일간 등 뷰 토글) |
| Legend | RN 화면 안 인라인 | components/ui/Legend.tsx | 🔵 어드민 전용 (캘린더 dot 범례) |
| PillToggle | — | components/ui/PillToggle.tsx | 🔵 어드민 전용 |
| AllowanceBadge | — | components/ui/AllowanceBadge.tsx | 🔵 어드민 전용 |
| VisibilityBadge | — | components/ui/VisibilityBadge.tsx | 🔵 어드민 전용 (공고 노출등급 S/A/B) |
| 가이드 패턴 | 모바일 (RN) | 어드민 (Next) | 상태 |
|---|---|---|---|
| InstructorPicker | AssignmentModals.tsx 내 | components/InstructorPickerModal.tsx | ✓ 양쪽 |
| CalendarModal | src/components/CalendarModal.tsx | — | 🟢 모바일 전용 |
| TimePickerModal | src/components/TimePickerModal.tsx | native <select> | 🌐 플랫폼 특수 |
| WheelColumn | src/components/WheelColumn.tsx | — | 🟢 모바일 전용 (휠 피커 부품) |
| 기능 | 모바일 (RN) | 어드민 (Next) | 비고 |
|---|---|---|---|
| Screen 래퍼 | Screen.tsx · KeyboardScreen.tsx | (Next layout) | 🌐 RN-특수 |
| BackButton | src/components/BackButton.tsx | (Next Link) | 🌐 라우팅 차이 |
| FileUpload | src/components/FileUploadField.tsx | — | 🟢 모바일 전용 |
| ImageUpload | src/components/ImageUploadField.tsx | — | 🟢 모바일 전용 |
| MaskedField | src/components/MaskedField.tsx | — | 🟢 모바일 전용 (전화번호 마스킹) |
| ScheduleCalendar | src/components/ScheduleCalendar.tsx | (어드민 schedule 페이지 인라인 그리드) | 🌐 인터랙션 차이 |
| ProfileHero·ProfileTabs | ProfileHero.tsx · ProfileTabs.tsx | — | 🟢 모바일 전용 (강사 본인 프로필 화면) |
| PostCard | src/components/PostCard.tsx (도메인 카드) | (어드민 페이지 내 인라인 row) | 🌐 도메인-특수 |
색·타입·간격·라디우스·고도·모션·z-index·상태 chip — 모든 디자인의 원자.
4계열 — Brand · Neutral · Semantic · Category. hex 절대 공유 금지.
각 색에 600 (hover/pressed) · 700 (강조 텍스트) 단계도 정의되어 있음. 위는 50/500 두 단계만 표시.
각 카테고리는 50/700 두 단계만 정의. Semantic 과 hex 가 절대 겹치지 않도록 분리됨 (프로그램=teal · 실시지원=orange · 교사연수=rose).
캘린더 그리드의 일·토 요일 시각 구분 전용 색. 캘린더 외 사용 금지 — Semantic / Category 와 의미가 다르다 (위험·주요액션·교육유형 의미가 아니라, 단순 "주말 표시" 의 시각 관행).
토큰: cal-sun-600 (#DC2626) · cal-sat-600 (#2563EB).
어드민 Tailwind config + 모바일 theme.colors 양쪽에 동일 hex 정의.
인라인 hex (text-red-600 / #E84040 등) 사용 금지.
강사 평점 표시 전용 황금 톤. 평점 자리 외 사용 금지 — warning 과 hex 가 비슷하지만 의미가 다르다 (경고 ≠ 평점). 별 ⭐ 아이콘 + 숫자 chip 한정.
| 토큰 | hex | 용도 |
|---|---|---|
rating-bg | #FFF8EC | 평점 chip 배경 |
rating-text | #B86A00 | 평점 숫자 텍스트 |
rating-star | #F5A623 | 별 ⭐ 아이콘 색 |
어드민·모바일 양쪽에 동일 hex 정의. warning-50/700 과 hex 비슷해도 의미 분리 위해 별도 토큰 사용.
8단계 타입 스케일. 임의 폰트 크기 금지.
폰트 패밀리: Pretendard → 시스템 폰트 fallback. Pretendard 는 한글·영문·숫자 모두에서 균형 잡힌 모던 산세리프.
예외 — 모바일 휠 피커: RN 시간/날짜 휠 피커 (WheelColumn) 는 시각 가독성 우선으로 8단계 외 fontSize (비활성 20px / 활성 28px) 허용. 휠 피커 외 자리는 8단계만.
위계는 3 차원 (사이즈 + 굵기 + 색)으로 구성. 한 차원만 바꿔도 위계 표현 가능 — 다 강조하면 위계가 무너진다.
| 차원 | 강도 | 예시 |
|---|---|---|
| 사이즈 | 가장 강함 | number (24) vs body (15) — 한눈에 차이 |
| 굵기 | 중간 | 700 vs 500 — 같은 사이즈에서 강조 |
| 색 | 가장 약함 | neutral-900 vs neutral-500 — 미세한 위계 |
| 역할 | 사이즈 | 굵기 | 색 |
|---|---|---|---|
| 가장 큰 정보 (가격, 인원 등) | number 24 | 800 | neutral-900 |
| 페이지 제목 | title 22 / display 32 | 800 | neutral-900 |
| 섹션 제목 | section 19 | 800 | neutral-900 |
| 카드 제목 | cardTitle 18 | 700 | neutral-900 |
| 본문 강조 | body 15 | 700 | neutral-900 |
| 본문 (기본) | body 15 | 500 | neutral-700 ~ 900 |
| 부제 / 메타 | caption 13 | 500 | neutral-500 |
| 라벨 (DL Row 등) | caption 13 | 600 | neutral-500 |
| uppercase 미니 라벨 | micro 11 | 600 + uppercase | neutral-500 |
| 의도 | Tailwind 클래스 | 적용 결과 |
|---|---|---|
| display | text-ds-display | 32 / 800 / 40 |
| title | text-ds-title | 22 / 800 / 30 |
| section | text-ds-section | 19 / 800 / 26 |
| cardTitle | text-ds-cardTitle | 18 / 700 / 24 |
| number | text-ds-number | 24 / 800 / 30 |
| body | text-ds-body | 15 / 500 / 22 |
| caption (default 600) | text-ds-caption | 13 / 600 / 18 |
| caption + medium override | text-ds-caption font-medium | 13 / 500 / 18 (부제용) |
| micro | text-ds-micro | 11 / 600 / 14 |
scale 외 사이즈 (12, 14) 가 필요하면 Tailwind 기본 text-xs (12px) / text-sm (14px) 사용. text-[XXpx] 임의값 금지.
Don't:
· 모든 텍스트에 font-semibold (600) 적용 — 위계 무너짐, "다 굵어 보임" (이게 가장 흔한 실수)
· 부제에 semibold/bold — 부제는 medium (500), 색으로 위계 (neutral-500)
· 한 카드에 굵은 텍스트 (700+) 3 개 이상 — 시선 분산. 주목 1~2 개로 제한
· caption (13) 사이즈에 700 이상 굵기 — 작고 굵으면 부담스러움. 600 까지
· body 15 + bold + neutral-900 다 한 번에 — 강조 너무 강함. 사이즈 ↑ 또는 색만 ↑ 중 하나로
10단계 스케일. 4의 배수만. (상세: § 운영 룰 8)
7단계. 부품 크기에 비례.
| v2 토큰 | px | Tailwind 클래스 | 용도 |
|---|---|---|---|
| r-xs | 4 | rounded 또는 rounded-sm | 배지, 인라인 태그 |
| r-sm | 8 | rounded-lg | 버튼, 인풋, 작은 칩 |
| r-md | 12 | rounded-xl | banner, 작은 카드 |
| r-lg | 16 | rounded-2xl | 카드 (기본), 모달 |
| r-xl | 22 | (custom) | 큰 카드 |
| r-2xl | 28 | rounded-3xl (24, 근사) | 히어로 |
| r-pill | 999 | rounded-full | 칩, 토글, 배지 |
주의: shadcn legacy rounded-md = 6px (var(--radius) 기반) 이라 v2 r-md (12px) 와 다름.
버튼·인풋엔 rounded-lg (8px = v2 r-sm), banner 에는 rounded-xl (12px = v2 r-md), 카드는 rounded-2xl (16px = v2 r-lg) 사용. rounded-md 직접 사용 권장 안 함.
5단계 + 포커스 링. 정보 위계와 비례.
Duration 5단계 + Easing 4종. 임의 ms 금지.
호버, 마이크로 인터랙션
기본 — 모달 등장 등 대부분
큰 화면 전환
페이지 진입, 강조 애니메이션
| 이름 | cubic-bezier | 용도 |
|---|---|---|
ease-out | (0.0, 0.0, 0.2, 1) | 들어올 때 (가장 흔함) · 모달·토스트 등장 |
ease-in | (0.4, 0.0, 1, 1) | 나갈 때 · 모달 닫힘, 화면 이탈 |
ease-in-out | (0.4, 0.0, 0.2, 1) | 양방향 · 슬라이더, 토글 |
linear | linear | 일정 속도 · 진행률·로딩 |
| 이름 | 변화 | 시간 | 용도 |
|---|---|---|---|
modalEnter | fade + scale 0.95→1 | base · ease-out | 모달 등장 |
toastSlide | translateY(-100%)→0 + fade | base · ease-out | 상단 토스트 등장 |
pulse | opacity 1↔0.55 | 2s infinite · ease-in-out | "교육중" 칩 · 진행 중 강조 |
5단계 레이어 (base · sticky · overlay · modal · toast). 임의 숫자 금지.
상태 chip — 외곽선·점·라벨 단일 형태. 진행 상태만 채움/굵기/pulse 변형.
기본 진행 단계 (모집중·교육대기·확인)
bg: neutral-0
border: {sem}-500
text: {sem}-700
완결 단계 (교육완료·정산완료) — bold
bg: {sem}-50
border: {sem}-500
text: {sem}-700
weight: bold
진행 중 (교육중) — 가장 주목해야 할 한 상태
bg: neutral-0
border: {sem}-500
text: {sem}-700
weight: bold
animate: pulse
버튼·인풋·카드·모달·뱅크 + 페이지 레이아웃·폼·정보 패널·Empty·Alert·Overlay.
공용 부품. 토큰 조합으로 정의.
사이즈: sm 32h · md 40h · lg 48h. 한 화면에 Primary 는 최대 1개 — 가장 중요한 액션만.
본문 라인 — 핵심 속성 1 / 속성 2 / 속성 3
bg: neutral-0 · border: neutral-200 · radius: lg(16) · padding: 2xl(24) · shadow: card
scrim: scrim-modal (0.6) · content radius: lg · shadow: floating · max-width: 420 · padding: 2xl
어드민 코드에서 가이드 컴포넌트를 직접 사용. 인라인 className 반복 금지 (상세: § 10.7 액션 슬롯).
| 가이드 | import 경로 | props 요약 |
|---|---|---|
| Button | @/components/ui/Button | variant=primary/secondary/ghost/destructive · size=sm/md/lg |
| AlertDialog (11.1) | @/components/ui/AlertDialog | variant=info/warning/danger/success · confirmVariant=primary/destructive |
| ActionSheet (11.4) | src/components/ActionSheet.tsx (모바일) | title · message? · options[{key,label,variant?}] · cancelLabel? — useDialog().actionSheet() 호출 → Promise<string|null> |
| InstructorPickerModal | @/components/InstructorPickerModal | candidates · onConfirm · children (추가 필드) |
| Banner (10.3) | @/components/ui/Banner | variant=info/warning/danger/success · icon · children · 1px neutral-200 border |
| Card (10.2) | @/components/ui/Card | variant=standard/subtle/hero · className · children |
| PillToggle | @/components/ui/PillToggle | checked · onChange · label · 활성=success-50 + ✓, 비활성=outline neutral |
| DLRow (10.4) | @/components/ui/DLRow | label · value · labelWidth — 정보 패널 dt/dd 한 줄 |
| AllowanceBadge | @/components/ui/AllowanceBadge | name · amount · locked · onRemove — 수당 칩 (활성/잠금 분기) |
| PageContainer (10.1) | @/components/ui/PageContainer | density=default/compact/wide · className · children — 페이지 최상위 래퍼 (p-8 + space-y) |
| PageHeader | @/components/ui/PageHeader | title · description · actions slot — h1 text-ds-title |
| CardHeader (10.2) | @/components/ui/CardHeader | title · description · actions slot — h3 text-ds-cardTitle |
| FilterToolbar (10.8) | @/components/ui/FilterToolbar | dimensions · value · onChange · search · onSearchChange · searchPlaceholder — B 패턴 (필터 칩 누적 + "+필터 추가" popover + 우측 검색). client |
| FilterDimension (type) | @/components/ui/FilterToolbar | key · label · icon · desc · values[{value, label, optgroup}] · group · required — 페이지별 차원 정의 |
| DataTable | @/components/ui/DataTable | columns (key · header · cell · align · width · show) · rows · rowKey · emptyMessage — generic <T> |
| Pagination (10.8) | @/components/Pagination | state · total · buildHref · unit — URL Link 기반. 좌(범위·총합) · 중(번호) · 우(페이지당 토글) |
| ClientPagination (10.8) | @/components/ClientPagination | page · perPage · total · onPageChange · onPerPageChange · unit — client state 변형. 시각은 Pagination 과 동일 |
| VisibilityBadge | @/components/ui/VisibilityBadge | tier=S/A/B/null — S만 / A↑ / B↑ / 전체 (brand 톤) |
| buttonClasses (helper) | @/components/ui/button-classes | variant · size · extra — Next Link 등 비-button 엘리먼트가 가이드 Button 외관 사용. server-safe (10.7) |
| SegmentedControl | @/components/ui/SegmentedControl | options: [{href, label, active}] · ariaLabel — 2-3 옵션 상호배타 토글 (월간/주간/일간 등) |
| Legend | @/components/ui/Legend | label · dotColorClass — 캘린더 색 안내 dot 범례. CHIP_DOT_COLOR 매핑 함께 사용 |
추가 컴포넌트 (Input · Toast 등) 는 추출 시점에 본 표 갱신.
반복 패턴 — 박스·간격·상태.
페이지 위계: Page → Section → Sub-section → Card. 각 위계마다 정해진 세로 간격을 사용한다.
페이지 컨테이너 max-width: 1280px 가운데 정렬 권장. 그 이상 넓이는 가독성 저하.
Card 는 5 영역의 조합. 모두 옵션 — 필요한 것만 사용. 영역 사이는 24px (sp-2xl) 분리.
Don't: Card 안에 또 Card 넣지 않기 — divider 로 영역 구분. Border 와 Shadow 둘 다 쓰는 게 기본 (Standard 변형).
Banner — 페이지·카드 상단의 정적 알림. Toast — 화면 우상단 일시 알림 (3초). Inline message — 인풋 아래 에러 등 흐름 안.
| 의미 | bg | border (1px) | text |
|---|---|---|---|
| Info | info-50 | neutral-200 | info-700 |
| Warning | warning-50 | neutral-200 | warning-700 |
| Danger | danger-50 | neutral-200 | danger-700 |
| Success | success-50 | neutral-200 | success-700 |
구조: padding 12px 16px · border-radius r-md(12) · border 1px neutral-200 (4면 균일) · 아이콘 + 텍스트 + 옵션 닫기.
의미는 bg 색 + 텍스트 색으로 전달. border 는 형태 분리용 neutral 톤 — 한 면만 강조하는 left-stripe 패턴 안 씀.
Read-only 정보를 의미 단위로 묶어서 표현. 한 그룹은 7~8개 필드 이내. 그룹마다 emoji + 헤더 + divider, 그 아래 Label-Value 가로 페어 list.
자유 입력 필드. 그룹 안 collapsible 로 배치.
| 구성 요소 | 사양 |
|---|---|
| 외곽 카드 | Standard Card — bg-neutral-0 + border-neutral-200 + r-lg(16) + p-2xl(24) + shadow-card |
| 그룹 그리드 | 2 column · gap-x sp-3xl(32) · gap-y sp-2xl(24) |
| 그룹 헤더 | emoji + 14/700 neutral-700 · padding-bottom 8 · border-bottom 1px neutral-200 · margin-bottom 12 |
| DL Row label | caption (13/600 neutral-500) · 너비 96px 고정 |
| DL Row value | body (13~15/500 neutral-900) · flex 1 |
| DL Row 행간 | 10px (sp-sm + 2) |
| Memo / Description | 그룹 안 collapsible (📝 + 토글) · 그룹 dl 아래 8~12 간격 |
그룹 분리 원칙: 도메인 단위로 묶음. "공고 정보" / "학교 정보" / "강사 정보" / "정산 정보" 등. 한 그룹 7~8 필드 초과면 sub-그룹 또는 별도 카드로 분리.
Don't: 4-column field grid (deprecated · 공간 비효율) · Label/Value 같은 무게 (위계 무너짐) · 한 그룹에 10+ 필드 (스캔 어려움) · 메모를 본 그룹 밖 별도 카드로 (그룹 안 collapsible 권장).
데이터가 없거나·로딩 중이거나·실패한 상태. 화면이 비어 보이지 않도록 명시적 처리.
| 상태 | 구성 | 패딩 |
|---|---|---|
| Empty | 아이콘(48) + Title (cardTitle) + Subtitle (body neutral-500) + Action | 32px+, 가운데 정렬 |
| Loading < 1초 | Spinner small (16~24px) | flow 안에 inline |
| Loading 1~3초 | Skeleton (실제 콘텐츠 형태 모방) | 실제 카드 위치 유지 |
| Loading 3초+ | Skeleton + "로딩 중..." 텍스트 | — |
| Error | Danger banner (10.3) + 재시도 버튼 | — |
Don't: "데이터 없음" 만 적기 (다음 액션 제시 필요) · 무한 회전 스피너 (스켈레톤 권장) · Stack trace 그대로 노출.
입력이 필요한 화면 (등록·수정·설정) 의 표준 패턴. Form Field · Form Section · Required marker · Inline validation 4가지를 일관되게 적용.
| 요소 | 스펙 |
|---|---|
| Form Section | Standard Card (r-lg, p-2xl, shadow-card) · 헤더 16/700 + border-b divider · 내부 form-grid |
| Form Section title | 16/700 neutral-900 · padding-bottom 8 · border-b 1px neutral-200 · margin-bottom 16 |
| Form Section ↔ Section | sp-3xl (32px) 세로 간격 |
| Form Grid | 2 column · gap-x sp-lg(16) · gap-y sp-md(12) |
| Form Field | label + input + (helper/error) 세로 stack · gap-y 6px |
| Form Label | caption (13/600 neutral-700) — 필드 라벨이라 약간 진한 톤 |
| Required marker | * · 빨간 색 (danger-500) · 라벨 끝에 4px 띄움 · font-bold |
| Form Input | height 36 (sm) 또는 40 (md) · padding x12 · r-sm(8) · border neutral-200 · focus border brand-500 + ring 3px brand/15 |
| Form Textarea | min-height 72 · padding 8x12 · resize vertical |
| Form Helper text | micro (11/500 neutral-500) · input 아래 |
| Form Error message | micro (11/500 danger-700) · input 아래 (helper 와 같은 자리, 등장 시 helper 대체) · ⚠ 아이콘 prefix 권장 |
| Error 시 Input | border danger-500 · bg danger-50 (살짝 핑크) · focus 시 ring 도 danger 톤 |
| Disabled 시 Input | bg neutral-100 · text neutral-500 · cursor-not-allowed |
| Form Actions | 우측 정렬 · gap-2 (8px) · padding-top sp-lg + border-top divider · Submit Primary md, Cancel Secondary md |
| Checkbox (단독) — 어드민 | <label> wrap 방식 · gap-1.5 (6px) 라벨 ↔ 입력 · text-ds-caption (13/600) neutral-700 · accent-brand-500 · cursor-pointer · disabled 시 opacity-50 + cursor-not-allowed |
| Checkbox 그룹 — 어드민 | div 컨테이너 · flex flex-wrap · gap-x-4 gap-y-2 · 그룹 안에 <Checkbox> 여러개 |
📱 모바일은 OptionGroup 칩으로 대체: RN 에서 네모 체크박스는 손가락 hit 영역 작아 칩 패러다임 사용 (FormFields::OptionGroup · MultiOptionGroup). 같은 의미 (다중 선택) 를 어드민은 ☑, 모바일은 활성 chip 으로 표현. § 0.1 매핑 표 △ (UX 차이) 분류.
| 시점 | 적합 케이스 |
|---|---|
| onChange | 실시간 형식 검증 (전화번호 포맷, 숫자만 등) · 너무 빈번해서 주의 |
| onBlur | 권장 — 필드 떠날 때 검증 (필수 입력, 이메일 형식 등) |
| onSubmit | 전체 검증 — 필수 누락·서버 검증. onBlur 와 함께 |
Don't:
· Label 없이 placeholder 만 — 입력 시 라벨 사라져 사용성·접근성 저하
· Inline label (라벨이 input 좌측에) — 모바일·반응형에서 깨짐. 위쪽 stack 권장
· 필수 항목인데 * 표시 없음 — 사용자가 어떤 게 필수인지 모름
· helper text 와 error 둘 다 빨간색 — 구분 안 됨. helper 는 neutral, error 만 danger
· 폼 전체 에러 toast 만 — 어떤 필드가 문제인지 모름. 인라인 + 토스트 병행
· Submit 버튼이 회색 (Secondary) — Primary 가 명확해야 함
✅ CANONICAL
흰 bg + 1px neutral-200 보더 (form-input 톤, 폼 전체와 시각 통일) + 18/700 큰 굵은 숫자 (금액의 중요성 표현). 콤마 자동, 우측 정렬, 원 suffix 우측.
| 배경 | neutral-0 (흰색) |
| 보더 | 1px neutral-200 · focus 시 brand-500 + 3px brand/15 ring |
| 패딩 | 12px 세로 · 14px 가로 |
| 라운드 | r-sm (8px) |
| 숫자 폰트 | 18px / 700 · neutral-900 · 우측 정렬 · 콤마 자동 |
| 원 suffix | 14px / 600 · neutral-700 · wrapper 우측 (input 안 X) |
| 키보드 | 모바일 number-pad · 어드민 inputMode="numeric" |
| 에러 시 | 보더 danger-500 · 배경 danger-50 |
| 비활성 | 배경 neutral-100 · 텍스트 neutral-500 |
적용 자리: AllowanceAddModal 금액, ObserverManageModal 참관비, CreatePostScreen 페이 입력 (1차시·연강·공강), 향후 모든 금액 입력. 컴포넌트는 FormFields::MoneyInput primitive 와 WonInputField (라벨 동반 wrapper).
날짜 입력은 Calendar Modal Trigger 패턴 사용. 텍스트 표시 + 📅 아이콘 → 클릭 시 캘린더 모달이 열려 날짜 선택. 잘못된 날짜 입력 불가능.
| 요소 | 스펙 |
|---|---|
| 구조 | 전체가 <TouchableOpacity> (모바일) / <button> (어드민) — 텍스트 + 우측 📅 아이콘. 클릭 시 CalendarModal 띄움 |
| 저장 포맷 | ISO YYYY-MM-DD (DB·API) |
| 표시 포맷 | 본문에선 2026-05-15 (금) — 요일 한글 1자 동반 (가독성) |
| 모달 | CalendarModal (월 단위, prev/next 화살표, 일·토 색 분리, today bold). overlay scrim, 닫기 버튼 |
| 최소·최대 | caller 가 props 로 전달 (예: 강의일 = 오늘 이후) |
| 비활성 | 버튼 opacity 0.4 + 클릭 차단 |
적용 자리: CreatePostScreen 강의일 (이미 사용 중), 향후 생년월일·일정 입력 등 모든 날짜 자리. 컴포넌트는 CalendarModal + 트리거 버튼 (DateInputField 추출 예정).
연락처는 마스킹 자동 패턴 사용. 사용자는 11자리 숫자만 입력 → 자동으로 010-1234-5678 형태로 표시·저장. 폼 안에서 다른 텍스트 input 과 시각 통일.
| 요소 | 스펙 |
|---|---|
| 입력 가능 자리 | 숫자만 (0-9). 사용자가 - 직접 입력해도 무시 (다 stripping 후 재포맷) |
| 마스킹 규칙 | 3자리 → XXX, 4~7자리 → XXX-YYYY, 8~11자리 → XXX-YYYY-ZZZZ |
| 저장 포맷 | 표시·저장 모두 010-1234-5678 통일 (- 포함). DB 일관성 |
| 키보드 | 모바일 keyboardType="phone-pad" · 어드민 inputMode="tel" |
| 최대 자리 | 11자리 (slice 차단). 옛 011/016 같은 10자리도 동일 패턴 (XXX-YYY-ZZZZ) |
| 검증 | 11자리 미달 시 helper text 로 안내 ("연락처 11자리를 입력해주세요") |
| placeholder | 010-1234-5678 |
적용 자리: SignUp 연락처, MyInfoScreen 본인 연락처, InstructorProfileScreen 강사 연락처 입력, 향후 모든 휴대폰 번호 입력. 컴포넌트는 FormFields::PhoneInput primitive + PhoneInputField (라벨 동반 wrapper).
페이지·카드·모달 어디든 "액션 영역" 은 동일한 룰을 따른다. 위치(컨테이너)는 슬롯마다 다르지만, 내부 레이아웃·정렬 우선순위·오버플로 규칙은 5자리 모두 동일. 슬롯마다 따로 결정하면 정합성이 깨진다.
| # | 슬롯 | 컨테이너 | 정렬 | 예시 |
|---|---|---|---|---|
| ① | PageHeader actions | 페이지 최상단 우측 | justify-between (좌측 title 과 한 행) | + 새 항목 / 편집 |
| ② | CardHeader actions | Card 영역 상단 우측 | justify-between (좌측 title 과 한 행) | + 항목 추가 / 편집 |
| ③ | Card 하단 ACTIONS | Card 영역 마지막 줄 | justify-end (우측 끝) | 저장 · 취소 / 삭제 |
| ④ | Modal·AlertDialog 푸터 | 다이얼로그 하단 | justify-end (우측 끝, destructive 좌측은 예외) | 취소 / 실행 |
| ⑤ | Page bottom actions | PageContainer 마지막 | justify-end (우측 끝) | 등록 · 수정 저장 (Form) |
flex items-center gap-2
단일 액션 → 그대로
주요 + 보조 1쌍 → [Secondary] [Primary] (Primary 가 오른쪽 끝)
3개 이상 → … 드롭다운 메뉴로 묶음
destructive 포함 → 다른 액션과 거리 (gap-3) 또는 별도 그룹
| 슬롯 | 기본 사이즈 | 비고 |
|---|---|---|
| ① ② ③ ⑤ | md (h-10) | 일반적인 페이지·카드·폼 모두 동일 |
| ④ Modal | md (h-10) | 좁은 모바일 모달 (max-w-sm) 만 sm (h-8) 허용 |
| 밀집 리스트 안 row | sm (h-8) | OfferAssignments 같은 inline action 한정 |
flex-1 min-w-0 + 길면 truncateflex-shrink-0 + whitespace-nowrap (절대 줄바꿈 금지)| 슬롯 | 모바일 (RN) | 어드민 (Next) |
|---|---|---|
| ① PageHeader.actions | PageHeader.tsx | components/ui/PageHeader.tsx · actions prop |
| ② CardHeader.actions | — | components/ui/CardHeader.tsx · actions prop |
| ③ Card 하단 | — | Card 안 마지막 div · flex items-center justify-end gap-2 |
| ④ Modal 푸터 | AlertBanner.tsx 내 | components/ui/AlertDialog.tsx 내장 · InstructorPickerModal children |
| ⑤ Page bottom | (인라인 RN View) | PageContainer 마지막 div · flex items-center justify-end gap-2 |
모든 액션 버튼은 @/components/ui/Button 사용. 인라인 className 으로 버튼 스타일 새로 작성하지 말 것 — 사이즈·variant 흩어짐의 시작.
className="bg-... border-... rounded-..." 로 가이드 Button 외관만 흉내 — 외관 유사해도 사이즈·variant 시스템 안 따름
/posts·/instructors·/schedule·/education-schedule·/posts/complete-batch·/hq-users 등 모든 리스트 페이지가 따르는 공통 룰. 페이지마다 다르게 결정하면 사용자 학습 비용 누적. 본 명세는 어드민 코드 (components/ui/FilterToolbar.tsx, components/Pagination.tsx, lib/pagination.ts) 의 실제 구현과 일치한다.
B 패턴 (Notion DB · Vercel Dashboard 영감) — 필터 칩 누적식 + "+필터 추가" popover + 우측 검색 + 표 하단 페이지네이션. 옛 패턴 (FilterBar form + 표 위 페이지네이션) 은 deprecated.
① PageHeader 제목 + 우측 액션 (예: + 새 항목)
② FilterToolbar [필터 칩들] [+필터 추가] ...flex-1... [검색 input]
③ DataTable 실제 데이터
④ Pagination 표 하단 — [범위·총합] [페이지 번호] [페이지당 토글]
옛 FilterBar (form 래퍼 + select 들) 를 대체하는 클라이언트 컴포넌트. 각 필터 차원을 칩으로 표현하고 popover 로 값을 바꾼다.
flex-1 으로 밀어냄), w-56 h-8, 🔍 아이콘. placeholder 에 검색 범위 명시 ("학교·지역 검색")?q= 자동 반영. 별도 제출 버튼 없음 (Enter 도 불필요)router.replace(`?${...}`). useTransition 으로 부드러운 전환. ?page= 는 필터/검색 변경 시 항상 제거FilterDimension[] (key · label · icon · desc · values[]). values 는 single-select. optgroup 으로 묶기 가능 (예: '내 담당' / '다른 담당자')| 페이지 | 차원 (URL 키) | 검색 |
|---|---|---|
/posts | 단계 (chip) · 정렬 (sort) · 담당 (owner) | 학교·지역 (q) |
/instructors | 등급 (tier) · 평점 (rating) · 정산정보 (missing) | 강사 이름·연락처 (q) |
/schedule | 등급 (tier) · 평점 (rating) | 강사 이름·연락처 (q) |
/education-schedule | 단계 (phase) · 담당 (owner) | — |
/posts/complete-batch | 정렬 (sort) | 학교·지역 |
| 항목 | 값 | 비고 |
|---|---|---|
| 기본 | 20 | 페이지당 행 수 — 모든 리스트 페이지 공통 |
| 옵션 | [20, 50, 100] | 토글 3 종 — 다른 값 사용 금지 |
| URL 파라미터 | ?page=1&per_page=20 | 1-based · 다른 쿼리(?chip=, ?q= 등) 와 함께 보존 |
| 쿼리 검증 | 옵션 외 값은 default(20) | parsePagination() 에서 흡수 |
위치: DataTable 다음 줄, 표 하단 중앙. 검색·필터 영역과 분리 — 같은 row 에 섞지 않는다.
1–20 / 163건). 굵게 강조된 범위 + 회색 총합‹ 1 2 3 … 9 ›. 7페이지 이하면 그대로 다 표시, 초과면 양 끝 + 현재 주변만 (pageRange() 헬퍼)bg-neutral-900 text-neutral-0 굵게. 비활성 = text-neutral-300bg-neutral-900 text-neutral-0clampPage())total === 0) 시: Pagination 자체 비표시 (return null). DataTable empty row 가 안내max ≤ 1) 중앙 번호 영역 비움 — 좌측 카운트 + 우측 페이지당 토글만| 컴포넌트 | import | 사용처 |
|---|---|---|
Pagination (URL) | @/components/Pagination | SSR 페이지 — ?page=&per_page= Link 기반. buildHref 로 페이지 외 쿼리 보존. 기본 선택지. |
ClientPagination | @/components/ClientPagination | client state 로 데이터 가공하는 페이지 (예: CompleteBatchTable). props: page · perPage · total · onPageChange · onPerPageChange. 시각 디자인은 동일. |
FilterBar, FilterChip, FilterChipGroup 사용 — 삭제된 컴포넌트. 새 페이지는 FilterToolbar[20, 50, 100] 외로 (예: 25, 30, 200)Alert · Badge · Push. Banner/Toast (§ 10.3) 와 구분.
Modal (9. 컴포넌트) 의 변형 — 사용자 확인이 필요한 경우만 사용. 일반 폼·복잡한 UI 는 Modal 을 그대로 쓰고, "정말 하시겠어요?" 류는 Alert.
| 유형 | 아이콘 | Primary 버튼 | 예시 |
|---|---|---|---|
| Confirmation | info-50/700 (▲) | brand-500 | 완료확인 처리 |
| Destructive | danger-50/700 (⚠) | danger-500 | 공고 취소·삭제 |
| Warning | warning-50/700 (⚠) | brand-500 | 저장 안 하고 나가기 |
구조: 아이콘(40px, 토큰 색) + 제목(cardTitle) + 설명(body neutral-700, 2~3줄) + Cancel + Primary (우측 정렬, gap 8). max-width 420px.
Don't: Cancel 을 Primary 색으로 (실수 유발) · 5+ 줄 설명 (대신 Modal 사용) · 자동 dismiss (Alert 는 항상 명시적 액션 필요).
"여기에 안 본 게 있어요" 를 알리는 시각 마커. 3 종.
| 종류 | 크기 | 색 | 위치 |
|---|---|---|---|
| Dot | 10×10px (border 2px neutral-0 포함) | danger-500 | 아이콘 우상단 (-2, -2) |
| Count | min 18px, h 18, padding 0 5 | danger-500 + neutral-0 | 아이콘 우상단 (-6, -8) |
| NEW | auto, padding 2 6 | brand-500 + neutral-0 | 요소 우상단 |
위치 룰: 아이콘 위 → 우상단 / 메뉴 항목 옆 → 우측 / 탭바 아이콘 위 → 우상단.
Don't: 모든 항목에 Dot (신호 가치 사라짐) · 다양한 색 사용 (warning/info dot 등 — 한 의미로 통일) · 큰 dot 으로 시각 압도.
아래는 도입 시 결정해야 할 항목 골격. 실제 룰은 도입 시점에 본 가이드에 추가.
| 카테고리 | 대상 | 중요도 | 예시 |
|---|---|---|---|
| 지원 수락 | 강사 | 일반 | "○○중학교 해석연수에 배정됐어요" |
| 재배정 | 강사 | 중요 | "○○중학교 배정이 취소되었어요 (사유: 일정 변경)" |
| 마감 임박 | HQ | 일반 | "○○공고 D-1, 지원자 0명" |
| 완료확인 대기 | HQ | 일반 | "○○공고 교육 종료 — 완료확인 처리 필요" |
| 지급처리 완료 | 강사 | 일반 | "○○ 강의 지급처리됐어요" |
| 시스템 공지 | 전체 | 중요 | "이용약관 변경 안내" |
참고: 11.2 의 Badge/Dot 은 Push 와 무관하게 앱 내에서 작동. Push 도입 후엔 두 시스템이 서로 동기화 (push 도착 → in-app dot 켜짐).
Alert (11.1) 의 multi-option 변형. 3개 이상 옵션 중 하나 선택이 필요할 때만 사용. RN Alert.alert 의 OS 기본 multi-button 알럿을 브랜드 톤으로 대체.
옵션이 2개 이하면 Alert (11.1) 의 [Cancel] [Primary] 패턴이 더 자연스럽다 — ActionSheet 는 진짜 옵션 분기일 때만.
| 요소 | 스펙 | 비고 |
|---|---|---|
| scrim | scrim-modal (0.6) | Alert 와 동일 |
| content | r-lg · neutral-0 · elevation.floating | Alert 와 동일 |
| max-width | 320px | 모바일 좁은 화면 기준 — Alert 의 420 보다 작음 |
| padding | space-2xl (24) | Alert 와 동일 |
| 제목 | cardTitle 가운데 | Alert 와 동일 |
| 메시지 | body neutral-700 가운데 | 옵션 |
| 옵션 행 | h-48 · neutral-50 배경 · cardTitle text · 가운데 | 구분선 없이 gap-sm (8) |
| destructive 옵션 | text danger-600 | 삭제·취소 류 |
| 취소 행 | h-40 · neutral-100 배경 · text neutral-700 | 옵션과 분리된 위계 (gap-md 12) |
아이콘 미사용: Alert 와 달리 ActionSheet 는 아이콘 없음. 옵션 종류라 정보·경고·위험 톤이 의미 없고, 옵션 시각이 우선이라 단순화.
Don't: 옵션 5개 이상 (드롭다운 메뉴 사용) · 옵션끼리 시각 위계 다르게 (모두 동등 옵션) · destructive 를 우측 끝 (실수 유발 — 옵션 위치는 의미 없으므로 destructive 는 마지막 또는 별도 표시).
모달·드로어·드롭다운 뒤쪽 배경. 토큰만 사용.
| 토큰 | Tailwind 클래스 | 용도 |
|---|---|---|
| scrim-modal | bg-scrim-modal | 모달, 풀스크린 다이얼로그 |
| scrim-light | bg-scrim-light | 드롭다운, 툴팁 배경 |
다크모드·모바일 특수·접근성·아이콘·Voice & Tone — 출시 전 점검 항목.
다크모드 토큰 페어 (미구현, 레퍼런스).
| 역할 | 라이트 | 다크 |
|---|---|---|
| 페이지 배경 | neutral-50 #F8FAFC | neutral-900 #0F172A |
| 카드 / 모달 | neutral-0 #FFFFFF | slate-800 #1E293B |
| 보더 | neutral-200 #E2E8F0 | neutral-700 #334155 |
| 본문 텍스트 | neutral-900 #0F172A | neutral-100 #F1F5F9 |
| 보조 텍스트 | neutral-500 #64748B | slate-400 #94A3B8 |
| Brand (CTA) | brand-500 #2F80ED | blue-400 #60A5FA |
RN 앱 특화 룰 — Touch target · Safe area.
룰: 작은 아이콘이라도 패딩으로 감싸 최소 44, 권장 48 의 탭 영역을 확보. 32 이하는 사용 금지.
모든 화면 컨테이너는 SafeAreaView 또는 useSafeAreaInsets 를 사용. 상단·하단 시스템 영역 침범 금지.
| 위치 | iPhone (노치) | iPhone (홈바) | Android (3-button) |
|---|---|---|---|
| 상단 inset | 44~59px | — | 24px (status bar) |
| 하단 inset | — | 34px | 0~48px |
모바일 모든 화면 하단의 표준 탭바. 좌·우·아래 띄워 둥근 알약 모양으로 배치 — 콘텐츠 위에 떠 있는 형태.
| 속성 | 값 (토큰) | 비고 |
|---|---|---|
| 위치 | position: absolute | 화면 하단 — 콘텐츠 스크롤과 독립 |
| 좌·우 여백 | sp-md(12) | 화면 가장자리에서 12px 띄움 |
| 하단 여백 | sp-md(12) + insets.bottom | 홈바 영역 회피 |
| 높이 | 64px | 고정 (label + 아이콘 + 패딩) |
| 모서리 | r-2xl(28) | 알약 형태 |
| 그림자 | shadow-tabbar | 위쪽으로 살짝 떠있는 느낌 (0 -1px 4px rgba(0,0,0,0.06)) |
| 배경 | neutral-0 | 흰색 (라이트모드) |
| 탭 라벨 | ds-micro (11/600) | 아이콘 아래 라벨 |
| 활성 색 | brand-500 | 현재 탭 아이콘·라벨 |
| 비활성 색 | neutral-500 | 그 외 탭 |
| 배지 (안 읽음) | danger-500 점 또는 숫자 | r-pill · ds-micro |
Don't: 화면 하단에 직사각형 (전체 폭) 으로 붙이지 말 것 — 우리 앱 표준은 떠있는 알약. borderTopWidth 사용 금지 (그림자로 분리).
Android 그림자 주의: Android 일부 디바이스에서 음수 offset (위쪽 그림자) 이 잘릴 수 있음 — `elevation` prop 보강 또는 `BlurView` 대안.
채팅 화면 (말풍선·읽음 라인·system 메시지·tooltip 카드) 은 본 가이드의 일반 패턴과 시각 언어가 다름. 별도 도메인 문서 (docs/chat-design.md) 로 분리.
단, "읽음" 표시 색은 본 가이드 토큰과 hex 가 같지만 의미가 다르므로 (warning ≠ 읽음), 도메인 전용 토큰 1개를 본 가이드 § 1 에 등록 — chat-read.
| 토큰 | hex | 의미 |
|---|---|---|
chat-read-500 | #F59E0B | 채팅 "읽음" 가로 라인 — warning-500 과 hex 동일하나 의미 분리 |
chat-fileOnMine | rgba(255,255,255,0.12) | 본인 메시지(brand 배경) 위 첨부 박스 강조 — 흰 12% 반투명 |
말풍선·tooltip·system message 등 나머지 채팅 토큰은 docs/chat-design.md 참조.
WCAG 2.1 AA. 색·키보드·스크린리더.
본문 4.5:1 이상 · 큰 글자 (18pt+ or 14pt bold) 3:1 이상 · UI 컴포넌트·아이콘 3:1 이상.
| 토큰 조합 | 대비비 | 상태 |
|---|---|---|
| text-neutral-900 on neutral-0 | 16.8:1 | ✓ AAA |
| text-neutral-700 on neutral-0 | 10.4:1 | ✓ AAA |
| text-neutral-500 on neutral-0 | 5.2:1 | ✓ AA |
| text-brand-700 on neutral-0 | 7.5:1 | ✓ AAA |
| text-brand-500 on neutral-0 | 4.6:1 | ✓ AA (간신히) |
| text-info-700 on neutral-0 | 7.5:1 | ✓ AAA |
| text-success-700 on neutral-0 | 4.7:1 | ✓ AA |
| text-warning-700 on neutral-0 | 5.2:1 | ✓ AA |
| text-danger-700 on neutral-0 | 5.9:1 | ✓ AA |
| text-cal-sun-600 on neutral-0 | 5.0:1 | ✓ AA |
| text-cal-sat-600 on neutral-0 | 4.6:1 | ✓ AA (간신히 — 본문 한정. 굵게 권장) |
신규 토큰 추가 시 WebAIM Contrast Checker 로 검증 후 본 표 갱신.
focus:outline-none 단독 사용 금지. 반드시 시각 대체 (ring·shadow·border) 동반.
| 요소 | 표준 focus 표현 |
|---|---|
| Input · Select · Textarea | focus:border-brand-500 focus:shadow-elev-focus (3px ring brand-500/25) |
| Button (모든 variant) | focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 |
| Link · Tab · 필터 칩 (FilterToolbar) | focus:ring-2 focus:ring-brand-500 |
Don't: outline:none + 대체 없음. 키보드 사용자가 현재 위치 모름.
| 패턴 | 필수 ARIA | 예시 |
|---|---|---|
| 아이콘 단독 button (텍스트 없음) | aria-label |
<button aria-label="이전 달">←</button> |
| Modal · Dialog | role="dialog" aria-modal="true" aria-labelledby |
제목 h 에 id 부여 + 모달 root 가 참조 |
| Tab 그룹 | role="tablist" + 각 탭 role="tab" aria-selected + panel role="tabpanel" |
InstructorTabs · PayoutsTabs |
| Toggle button (active/inactive) | aria-pressed |
FilterToolbar 칩 · SegmentedControl ✓ 적용 완료 |
| Form 검증 | aria-invalid + aria-describedby (error 텍스트 id) |
Input invalid 상태 + helper/error 텍스트 연결 |
| Disclosure (펼침·접힘) | aria-expanded |
HistorySection 더보기 토글 |
색만으로 의미 전달 금지. 텍스트 라벨·아이콘·패턴 병행 필수.
| 자리 | 색 단독 위반 가능성 | 병행 보강 |
|---|---|---|
| StatusBadge (단계 칩) | 색만 다름 (info/warning/success) | 라벨 텍스트 (모집중/교육중/완료확인) 항상 표시 ✓ |
| Form 에러 | border-danger-500 만으로 부족 | error 메시지 인라인 + ⚠ 아이콘 권장 |
| 캘린더 토·일 색 | cal-sun/sat 만 다름 | 요일명 라벨 (일/토) 병행 ✓ |
| 정산 상태 (확정/취소/초안) | text 색만 다름 | 라벨 텍스트 + 취소는 line-through ✓ |
color: red 만으로 에러 표시 — 텍스트·아이콘 병행outline:none 단독 + 대체 없음 — 키보드 사용자 위치 상실role="dialog" 없음 — 스크린리더 컨텍스트 미인식aria-label 없음 — 스크린리더는 빈 button 으로 인식아이콘 라이브러리 · 사이즈 · 색 · 사용 패턴. lucide-react 표준.
lucide-react 채택. 1,000+ 아이콘 · 1.5px stroke · 24×24 grid · MIT · Tree-shakable.
RN 은 lucide-react-native.
| 고려 라이브러리 | 장점 | 단점 | 채택 |
|---|---|---|---|
| lucide-react | 풍부 · 1.5px stroke · MIT · RN 지원 | 커스텀 아이콘 추가 시 별도 | ✓ |
| heroicons | Tailwind 팀 · 깔끔 | 아이콘 수 적음 (300+) | |
| phosphor-icons | weight 6종 (thin~bold) | 패키지 큼 | |
| 이모지·텍스트 기호 | 설치 0 | 플랫폼별 렌더 다름·접근성 약함 | ❌ |
| 토큰 | 크기 | 용도 | 예시 |
|---|---|---|---|
icon-xs | 12×12 | 인라인 텍스트 옆 (caption) | locked 🔒, info ⓘ |
icon-sm | 16×16 | 버튼 안·메뉴 항목 | + 추가, ← 이전 |
icon-md | 20×20 | 탭바·툴바 (모바일) | 홈·검색·프로필 |
icon-lg | 24×24 | FAB·페이지 헤더 액션 | 편집·설정 |
Don't: 18, 22, 28 같은 임의 값 — 4 단계 외 사용 금지. 디자이너 제공 SVG 도 토큰 사이즈로 export.
기본 currentColor (텍스트 색 따라감) 사용. 의미 강조 시에만 v2 토큰 명시.
| 상황 | 색 | 예시 |
|---|---|---|
| 일반 (텍스트와 일체) | currentColor | 버튼 안 + 아이콘, 메뉴 항목 |
| 의미 강조 — 성공 | text-success-700 | 저장 완료, 배정 완료 |
| 의미 강조 — 경고 | text-warning-700 | 마감 임박, 미입력 알림 |
| 의미 강조 — 위험 | text-danger-700 | 삭제 버튼, 에러 인디케이터 |
| 비활성·보조 | text-neutral-500 | 읽음 표시, 부가 정보 |
| 패턴 | 구조 | 접근성 |
|---|---|---|
| 아이콘 + 텍스트 | <Icon size=16 /> 라벨 · gap-2 |
아이콘은 장식 — aria-hidden="true" |
| 아이콘 단독 button | <button><Icon /></button> |
반드시 aria-label (§ 15.4) |
| 상태 인디케이터 | 텍스트 옆 작은 icon (lock·warn·dot) | 색 + 아이콘 둘 다 (§ 15.5 색맹 보강) |
| 로딩 | <Loader2 className="animate-spin" /> |
aria-label="로딩 중" |
코드에 박혀있는 이모지·텍스트 기호 → lucide 컴포넌트 교체 매핑.
| 현재 | lucide | 위치 |
|---|---|---|
| 🔒 | Lock | AllowanceBadge · ObserverSection 잠금 표시 |
| ⚠ | AlertTriangle | master 배너 · PostForm lockSchedule · AlertDialog warning |
| ✓ | Check | PillToggle 활성 · CompletePostButton · AlertDialog success |
| ← | ChevronLeft | 캘린더 이전 (education-schedule, schedule) |
| → | ChevronRight | 캘린더 다음 · PostForm 시간 표시 |
| + (텍스트) | Plus | + 새 항목 · + 부속 추가 |
| × | X | 모달 닫기 · 칩 삭제 |
ChevronLeft 등 사용currentColor 또는 v2 토큰만aria-label 누락 — § 15.4 강제버튼 라벨 · empty state · error · success 카피 표준.
| 원칙 | 예시 (○ 권장) | 예시 (✕ 회피) |
|---|---|---|
| 명료 > 격식 | "공고가 등록됐어요" | "공고 등록이 완료되었습니다." (딱딱) |
| 능동 > 수동 | "강사 1명을 배정하세요" | "강사가 배정되어야 합니다" (수동) |
| 결과 > 절차 | "저장됨" | "저장 처리가 완료되었습니다" (장황) |
| 비난 > 안내 | "이메일 형식이 맞지 않아요" | "잘못된 입력입니다" (비난) |
"취소"/"닫기"/"뒤로"/"확인" 혼용 금지. 다음 표대로 통일.
| 자리 | 왼쪽 (보조) | 오른쪽 (주요) |
|---|---|---|
| 일반 modal (정보 입력) | 취소 | 저장 / 등록 / 추가 |
| 확인 modal (위험 액션) | 아니오 | 삭제 / 취소 / 확정 (구체 동사) |
| 알림 modal (단순 OK) | — | 확인 |
| 인라인 폼 (펼침) | 뒤로 | 적용 / 추가 (구체 동사) |
| 읽기 전용 modal | — | 닫기 |
| 드로어·시트 | — | 닫기 (X 아이콘 + aria-label) |
구체 동사 우선: "확인" 대신 "삭제"·"저장"·"제출" 같이 결과를 명시. 사용자가 "확인" 누른 다음 무엇이 일어날지 모르면 안 됨.
비어있는 자리에는 현재 상태 + 다음 행동 힌트 두 줄. "—" 만 쓰지 말 것.
| 자리 | 현재 상태 (1줄) | 다음 행동 (2줄, 선택) |
|---|---|---|
| 리스트 0건 | 아직 등록된 [항목] 이 없어요 | + 새 [항목] 등록 버튼 안내 |
| 필터 결과 0건 | 해당하는 [항목] 이 없어요 | "필터 초기화" 링크 동반 |
| 완료 후 (성공) | 모두 처리됐어요 | — |
| 권한 없음 | 이 페이지 접근 권한이 없어요 | "홈으로" 또는 관리자 문의 |
| 유형 | 패턴 | 예시 |
|---|---|---|
| 입력 검증 | "[필드] 가 [조건] 이에요" | "이메일 형식이 맞지 않아요" / "비밀번호는 8자 이상이어야 해요" |
| 네트워크·서버 | "[행동] 못 했어요. [재시도 안내]" | "저장 못 했어요. 다시 시도해주세요" |
| 권한 | "[행동] 권한이 없어요" | "이 공고는 작성자만 수정할 수 있어요" |
| 제약 위반 | "[조건] 이라서 [결과] 못 해요" | "이미 배정된 강사라서 취소할 수 없어요" |
금지: "에러 발생" / "실패했습니다" / "Unknown error" 같은 무의미 메시지. 항상 왜 + 무엇 명시.
| 상태 | 패턴 | 예시 |
|---|---|---|
| 저장 완료 (즉시) | "[항목] [동사]됐어요" | "공고가 저장됐어요" |
| 저장 완료 (인라인 표시) | "저장됨" (간결) | FormField 옆 작은 텍스트 |
| 로딩 (버튼) | "[동사] 중…" | "저장 중…" / "처리 중…" |
| 로딩 (페이지) | "[항목] 불러오는 중…" | "공고 목록 불러오는 중…" |
ADR (결정 기록) + 운영 룰 12조항. 변경 거버넌스의 단일 진입점.
핵심 결정의 왜·근거 기록 + 외부 벤치마크.
| 상태 | Accepted (2024-Q4) |
| 결정 | brand-500 = #2F80ED (밝은 파랑, Tailwind blue-500 과 비슷) |
| 맥락 | 처음 shadcn 템플릿의 무채색 (검정 primary) 에서 출발 → 학습·업무 도구라 신뢰감 + 차분함 필요 |
| 근거 | ① 파랑 = "신뢰·전문성" 보편 의미 (한국 SaaS 표준: 토스·카카오워크·노션 모두 파랑) ② 채도 60% 라 장시간 작업 피로 적음 ③ Semantic info 와 hex 공유 — 정보성 강조에 자연 |
| 대안 | 녹색 (성공 색과 충돌) · 보라 (특이하지만 SaaS 어색) · 회색 단색 (개성 부족) |
| 벤치마크 | 토스 #3182F6 (비슷) · Polaris #008060 (녹색·차별화) · Material #6750A4 (보라) |
| 상태 | Accepted |
| 결정 | display 32 / title 22 / section 19 / cardTitle 18 / number 24 / body 15 / caption 13 / micro 11 — 8단계 |
| 맥락 | Tailwind default (xs~9xl 12단계) 가 너무 자유 → 위계 흐림. 단계 줄여 시각 위계 강제 |
| 근거 | ① 어드민은 정보 밀도 중요 → 큰 단계 (5xl·6xl·7xl·8xl·9xl) 불필요 ② 8단계 = Material/Polaris 가 사용하는 단계 수 (5-8) ③ font-weight·line-height 도 묶어 토큰화 — 호출처 결정 부담 ↓ |
| 대안 | 5단계 (Material default — 너무 적음) · 12단계 (Tailwind default — 너무 많음) · em 기반 스케일 (px 직관적) |
| 벤치마크 | Material 5단계 (Display/Headline/Title/Body/Label) · Polaris 7단계 · iOS HIG 11단계 (Large Title~Caption2) |
| 상태 | Accepted |
| 결정 | 4의 배수 (4·8·12·16·20·24·32·40·48·64) — 임의 7px·15px·26px 금지 |
| 맥락 | Tailwind default 도 4-base — 표준 따름 |
| 근거 | ① 시각 리듬 — 4 배수가 화면에서 눈에 정렬 ② 디자이너·개발자 협업 — Figma 그리드 4·8 표준과 일치 ③ 토큰 수 ↓ — 결정 부담 ↓ |
| 대안 | 8-base (단계 부족) · em 기반 (반응형이지만 px 직관성 잃음) · golden ratio (1.618 — 어색) |
| 벤치마크 | Material 4-base · Polaris 4-base · Tailwind default 4-base · iOS HIG 8pt grid |
| 상태 | Accepted |
| 결정 | primary / secondary / ghost / destructive — 4 variant + sm/md/lg 3 size = 12 조합 |
| 맥락 | shadcn 기본 6 variant (default/destructive/outline/secondary/ghost/link) 에서 outline·link 제거 → 결정 부담 ↓ |
| 근거 | ① 한 화면 Primary 1개 (운영 룰 11) 강제 → primary·secondary·ghost·destructive 4개로 충분 ② link 는 인라인 텍스트 링크와 시각 충돌 → 별도 변형 안 만듦 ③ outline-* 는 가이드에 추가 가능성 열어둠 (필요 시 outline-destructive 등) |
| 대안 | 3 variant (primary/secondary/destructive) — ghost 자리 부족 · 6 variant (shadcn default) — 결정 부담 |
| 벤치마크 | Material 5 variant (Filled/Tonal/Outlined/Text/Elevated) · Polaris 4 (Primary/Default/Plain/Critical) · iOS HIG 3 (Filled/Tinted/Plain) |
| 상태 | Accepted (2026-Q1 완료) |
| 결정 | shadcn 의 HSL CSS 변수 시스템 (--background, --foreground 등) 폐기 → Tailwind config 의 직접 hex 토큰 (brand-500: #2F80ED 등) |
| 맥락 | shadcn 템플릿이 깔아둔 기본 시스템. 다크모드 자동 처리에 유리하지만 의미가 추상 (background/foreground/card/muted) — 어드민 컨텍스트에 부적합 |
| 근거 | ① 의미 토큰화: bg-card → bg-neutral-0 (수치 명시) — 디자이너·개발자 같은 언어 ② Tailwind 의 4-base 색 시리즈 (50/500/600/700) 와 일관 ③ Semantic (success·warning·danger·info) 분리로 의미 강제 ④ 다크모드는 Phase 2 — 도입 시점에 별도 토큰 추가 (현재 가이드 § 13 다크 매핑은 레퍼런스 단계) |
| 리스크 | 다크모드 도입 시 토큰 전체 재작성. 이미 가이드 § 13 에 매핑 명세 → 작업 양 명확. |
| 벤치마크 | 토스 직접 hex · Material direct (M3) · Polaris direct · Atlassian DS Tokens (semantic 토큰) |
신규 핵심 결정 (토큰 이름·구조 변경, 새 컴포넌트 추출 기준 등) 은 ADR 추가. 형식:
참고: ADR 패턴은 Michael Nygard 의 ADR template 따름. 소프트웨어 아키텍처 문서 표준.
가이드 전반에 흩어진 Don't 의 단일 진입점. 새 페이지·컴포넌트 작성 시 본 룰 위반 여부 먼저 검증.
'#xxxxxx' 직접 정의 금지.위 1-12 시스템 룰 외에, 각 컴포넌트·패턴 자체의 Don't 가 본문에 분산되어 있다. 빠른 참조용 색인.
| 자리 | 핵심 Don't | 점프 |
|---|---|---|
| 매핑 운영 룰 | 가이드·코드 동시 변경 / 인라인 발견 시 컴포넌트화 / ⚠ 항목은 백로그 / 🌐 미러 시도 금지 | § 0.2 |
| 액션 슬롯 | 한 슬롯에 같은 variant 2개 / 액션 4개 이상 (드롭다운 강제) / destructive 우측 끝 (실수 유발) / Cancel ghost 를 우측 끝 | § 10.7 |
| 리스트 페이지 | 페이지 사이즈 [20,50,100] 외 (예: 25, 30) / 무한 스크롤 / 검색 디바운스 자동 제출 / "전체" 옵션 빠진 select | § 10.8 |
| Form 패턴 | 필수 항목인데 * 없음 / helper·error 같은 색 / Submit 회색 (Secondary) / 에러 toast 만 (필드 인라인 누락) | § 10.6 |
| Badge / Push | 모든 항목에 Dot (신호 가치 사라짐) / 다양한 색 (warning·info dot 혼용) / 큰 dot 으로 시각 압도 | § 11.2 |