v2 · 통합본

티쳐스온 디자인 시스템

토큰 · 컴포넌트 · 운영 룰 통합 명세.

Overview · 구현 매핑

가이드 패턴 ↔ 코드 파일 색인. 변경 시 단일 진입점.

0구현 매핑 — Source of Truth

가이드 패턴 ↔ 두 코드베이스 (모바일 RN · 어드민 Next) 컴포넌트 파일 색인.

0.1 패턴 ↔ 파일 매핑

모바일은 React Native + StyleSheet, 어드민은 Next.js + Tailwind. 1:1 미러 불가 — 상태 컬럼으로 일치도 표시.

상태 범례   ✓ 양쪽 양 플랫폼 동등 추출 · ⚠ 한쪽 인라인 반대편 추출 작업 항목 · △ UX 차이 플랫폼 특성상 인터페이스 다름 · 🔵/🟢 한쪽 전용 반대편 해당 패턴 없음 · 🌐 플랫폼 특수 본질적으로 다른 구현

기본 컴포넌트 (가이드 9·10·11)

가이드 패턴모바일 (RN)어드민 (Next)상태
Button (9.2)인라인 TouchableOpacitycomponents/ui/Button.tsx⚠ 모바일 추출 필요
Input (9.4 · 10.6)src/components/FormFields.tsx::InputFieldcomponents/ui/Input.tsx✓ 양쪽
Textarea (9.4)FormFields::InputField multilinecomponents/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::StepCardcomponents/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 과 별)
PageHeadersrc/components/PageHeader.tsxcomponents/ui/PageHeader.tsx✓ 양쪽
CardHeader (10.2)—components/ui/CardHeader.tsx🔵 어드민 전용
SectionHeadersrc/components/SectionHeader.tsx인라인 h2/h3⚠ 어드민 추출 필요
EmptyStatesrc/components/EmptyState.tsx인라인 div⚠ 어드민 추출 필요
Greetingsrc/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 변형 둘)

Chip · Badge · 도메인 마이크로

가이드 패턴모바일 (RN)어드민 (Next)상태
StatusBadge (8)src/components/StatusDot.tsx (점)components/StatusBadge.tsx (라벨)△ UX 차이
FilterChip (8)src/components/FilterChip.tsxdeprecated — FilterToolbar 가 흡수🟢 모바일 전용 (어드민은 삭제됨)
SegmentedControl—components/ui/SegmentedControl.tsx🔵 어드민 전용 (월간/주간/일간 등 뷰 토글)
LegendRN 화면 안 인라인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)상태
InstructorPickerAssignmentModals.tsx 내components/InstructorPickerModal.tsx✓ 양쪽
CalendarModalsrc/components/CalendarModal.tsx—🟢 모바일 전용
TimePickerModalsrc/components/TimePickerModal.tsxnative <select>🌐 플랫폼 특수
WheelColumnsrc/components/WheelColumn.tsx—🟢 모바일 전용 (휠 피커 부품)

플랫폼 특수 (양쪽 미러 불가)

기능모바일 (RN)어드민 (Next)비고
Screen 래퍼Screen.tsx · KeyboardScreen.tsx(Next layout)🌐 RN-특수
BackButtonsrc/components/BackButton.tsx(Next Link)🌐 라우팅 차이
FileUploadsrc/components/FileUploadField.tsx—🟢 모바일 전용
ImageUploadsrc/components/ImageUploadField.tsx—🟢 모바일 전용
MaskedFieldsrc/components/MaskedField.tsx—🟢 모바일 전용 (전화번호 마스킹)
ScheduleCalendarsrc/components/ScheduleCalendar.tsx(어드민 schedule 페이지 인라인 그리드)🌐 인터랙션 차이
ProfileHero·ProfileTabsProfileHero.tsx · ProfileTabs.tsx—🟢 모바일 전용 (강사 본인 프로필 화면)
PostCardsrc/components/PostCard.tsx (도메인 카드)(어드민 페이지 내 인라인 row)🌐 도메인-특수
0.2 매핑 운영 룰
  1. 가이드 ↔ 코드 동시 변경 — 가이드만 고치고 코드 안 바꾸면 명세서가 거짓이 됨. 반대도 마찬가지.
  2. 인라인 발견 시 컴포넌트로 승격 — 같은 className·StyleSheet 패턴이 2곳 이상에서 반복되면 추출 후 본 표에 등록.
  3. ⚠ 항목은 작업 백로그 — "한쪽 인라인" 상태 행은 다음 디자인 마이그레이션 PR 의 후보. 상태 컬럼이 ✓ 또는 △ 가 될 때까지 추적.
  4. 🌐 플랫폼 특수는 미러 시도 금지 — 본질적으로 다른 패러다임 (예: 휠 피커 vs 드롭다운) 은 억지로 통일하지 말 것. 같은 결과를 내는 다른 UI 로 처리.
  5. 새 컴포넌트 추가 절차: 코드 작성 → 가이드 9.X 또는 해당 섹션 명세 추가 → 본 표에 행 추가. 셋이 한 PR.

Foundation · 토큰

색·타입·간격·라디우스·고도·모션·z-index·상태 chip — 모든 디자인의 원자.

1색상

4계열 — Brand · Neutral · Semantic · Category. hex 절대 공유 금지.

Brand · 티쳐스온 블루
50

brand-50

#EFF6FF

알림 배너 배경

100

brand-100

#DBEAFE

선택된 칩, 달력 일정

500

brand-500

#2F80ED

CTA 버튼, 활성 탭, 링크

600

brand-600

#1665D8

CTA 호버 / 눌림

700

brand-700

#144FAB

진한 텍스트 강조

Neutral · 회색 톤 (Tailwind slate)
0

neutral-0

#FFFFFF

카드, 모달, 입력 필드

50

neutral-50

#F8FAFC

페이지 배경

100

neutral-100

#F1F5F9

비활성 칩, 빈 슬롯

200

neutral-200

#E2E8F0

보더 1px, divider

300

neutral-300

#CBD5E1

placeholder, disabled

500

neutral-500

#64748B

보조 텍스트, meta

700

neutral-700

#334155

부제, 강조 메타

900

neutral-900

#0F172A

본문, 모든 제목

Semantic · 의미가 있을 때만 (한 화면 동시 2종)
50

info-50

#EFF6FF

정보 배너 배경

500

info-500

#2F80ED

참고 메시지 (= brand)

50

success-50

#ECFDF5

성공 토스트 배경

500

success-500

#10B981

완료 아이콘 ✓

50

warning-50

#FFFBEB

마감 임박 배경

500

warning-500

#F59E0B

교육중 점 표시

50

danger-50

#FEF2F2

에러 토스트 배경

500

danger-500

#EF4444

삭제 버튼, ⚠ 아이콘

각 색에 600 (hover/pressed) · 700 (강조 텍스트) 단계도 정의되어 있음. 위는 50/500 두 단계만 표시.

Category · 도메인 카테고리 (chip 한 곳에서만)
해석연수 교사연수 프로그램 실시지원

각 카테고리는 50/700 두 단계만 정의. Semantic 과 hex 가 절대 겹치지 않도록 분리됨 (프로그램=teal · 실시지원=orange · 교사연수=rose).

Functional · 캘린더 주말 (한국 캘린더 표준)

캘린더 그리드의 일·토 요일 시각 구분 전용 색. 캘린더 외 사용 금지 — Semantic / Category 와 의미가 다르다 (위험·주요액션·교육유형 의미가 아니라, 단순 "주말 표시" 의 시각 관행).

일 토

토큰: cal-sun-600 (#DC2626) · cal-sat-600 (#2563EB). 어드민 Tailwind config + 모바일 theme.colors 양쪽에 동일 hex 정의. 인라인 hex (text-red-600 / #E84040 등) 사용 금지.

Functional · 평점 (별점)

강사 평점 표시 전용 황금 톤. 평점 자리 외 사용 금지 — warning 과 hex 가 비슷하지만 의미가 다르다 (경고 ≠ 평점). 별 ⭐ 아이콘 + 숫자 chip 한정.

★ 4.8
토큰hex용도
rating-bg#FFF8EC평점 chip 배경
rating-text#B86A00평점 숫자 텍스트
rating-star#F5A623별 ⭐ 아이콘 색

어드민·모바일 양쪽에 동일 hex 정의. warning-50/700 과 hex 비슷해도 의미 분리 위해 별도 토큰 사용.

2타이포그래피

8단계 타입 스케일. 임의 폰트 크기 금지.

display · 32 / 800 / 40서비스 명칭 · 페이지 타이틀
title · 22 / 800 / 30페이지·상세 헤딩
section · 19 / 800 / 26섹션 헤더 (h2)
cardTitle · 18 / 700 / 24카드·서브 섹션 타이틀
number · 24 / 800 / 30000,000 원
body · 15 / 500 / 22설명·안내문 등 일반 본문 텍스트.
caption · 13 / 600 / 18YYYY-MM-DD · 보조 메타
micro · 11 / 600 / 14부가 메타·툴팁

폰트 패밀리: 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 24800neutral-900
페이지 제목title 22 / display 32800neutral-900
섹션 제목section 19800neutral-900
카드 제목cardTitle 18700neutral-900
본문 강조body 15700neutral-900
본문 (기본)body 15500neutral-700 ~ 900
부제 / 메타caption 13500neutral-500
라벨 (DL Row 등)caption 13600neutral-500
uppercase 미니 라벨micro 11600 + uppercaseneutral-500

Tailwind 클래스 매핑 — 어드민 코드에서 사용

의도Tailwind 클래스적용 결과
displaytext-ds-display32 / 800 / 40
titletext-ds-title22 / 800 / 30
sectiontext-ds-section19 / 800 / 26
cardTitletext-ds-cardTitle18 / 700 / 24
numbertext-ds-number24 / 800 / 30
bodytext-ds-body15 / 500 / 22
caption (default 600)text-ds-caption13 / 600 / 18
caption + medium overridetext-ds-caption font-medium13 / 500 / 18 (부제용)
microtext-ds-micro11 / 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 다 한 번에 — 강조 너무 강함. 사이즈 ↑ 또는 색만 ↑ 중 하나로

3간격

10단계 스케일. 4의 배수만. (상세: § 운영 룰 8)

xs · 4px
아이콘 ↔ 텍스트 사이
sm · 8px
밀집된 인라인 요소
md · 12px
기본 인라인 갭
lg · 16px
카드 내부 여백·블록 간격
xl · 20px
중간 강조 블록
2xl · 24px
카드 패딩 (기본)
3xl · 32px
섹션 사이
4xl · 40px
큰 섹션 분리
5xl · 48px
페이지 상단·하단
6xl · 64px
랜딩·히어로 영역

4모서리 (Radius)

7단계. 부품 크기에 비례.

xs · 4px
배지, 인라인 태그
sm · 8px
버튼, 인풋, 작은 칩
md · 12px
중간 카드, 다이얼로그 일부
lg · 16px
카드 (기본), 모달
xl · 22px
큰 카드, 섹션 컨테이너
2xl · 28px
히어로 카드
pill · 999
칩, 토글, 배지

Tailwind 클래스 매핑 — 어드민 코드에서 사용

v2 토큰pxTailwind 클래스용도
r-xs4rounded 또는 rounded-sm배지, 인라인 태그
r-sm8rounded-lg버튼, 인풋, 작은 칩
r-md12rounded-xlbanner, 작은 카드
r-lg16rounded-2xl카드 (기본), 모달
r-xl22(custom)큰 카드
r-2xl28rounded-3xl (24, 근사)히어로
r-pill999rounded-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그림자 (Elevation)

5단계 + 포커스 링. 정보 위계와 비례.

sm미세 elevation, 호버
card기본 카드
md드롭다운, 팝오버
floating모달, 풀스크린
tabBar하단 탭바 (반전)
focus인풋 포커스 링

6모션

Duration 5단계 + Easing 4종. 임의 ms 금지.

Duration — 시간

fast

150ms · ease-out

호버, 마이크로 인터랙션

base

250ms · ease-out

기본 — 모달 등장 등 대부분

slow

400ms · ease-out

큰 화면 전환

slower

600ms · ease-in-out

페이지 진입, 강조 애니메이션

Easing — 가속·감속 곡선
이름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)양방향 · 슬라이더, 토글
linearlinear일정 속도 · 진행률·로딩
Named Animations — 이름붙은 애니메이션
이름변화시간용도
modalEnterfade + scale 0.95→1base · ease-out모달 등장
toastSlidetranslateY(-100%)→0 + fadebase · ease-out상단 토스트 등장
pulseopacity 1↔0.552s infinite · ease-in-out"교육중" 칩 · 진행 중 강조

7Z-index 계층

5단계 레이어 (base · sticky · overlay · modal · toast). 임의 숫자 금지.

z-base · 0
기본 콘텐츠
z-sticky · 10
sticky 헤더, 탭바
z-overlay · 20
드롭다운, 툴팁
z-modal · 30
모달, 다이얼로그
z-toast · 40
토스트 메시지

8Chip 컴포지션 패턴

상태 chip — 외곽선·점·라벨 단일 형태. 진행 상태만 채움/굵기/pulse 변형.

외곽선형

기본 진행 단계 (모집중·교육대기·확인)

모집중   교육대기
bg: neutral-0
border: {sem}-500
text: {sem}-700

채움형

완결 단계 (교육완료·정산완료) — bold

교육완료   정산완료
bg: {sem}-50
border: {sem}-500
text: {sem}-700
weight: bold

외곽선 + Pulse

진행 중 (교육중) — 가장 주목해야 할 한 상태

교육중
bg: neutral-0
border: {sem}-500
text: {sem}-700
weight: bold
animate: pulse

Components · 컴포넌트 & 패턴

버튼·인풋·카드·모달·뱅크 + 페이지 레이아웃·폼·정보 패널·Empty·Alert·Overlay.

9컴포넌트 프리미티브

공용 부품. 토큰 조합으로 정의.

Button — 4 variant × 3 size
Primary
Secondary
Ghost
Destructive

사이즈: sm 32h · md 40h · lg 48h. 한 화면에 Primary 는 최대 1개 — 가장 중요한 액션만.

Input — 텍스트 필드 상태
Default 기본 상태
Focus brand-500 보더 + focus 그림자
Error
올바른 이메일 형식이 아닙니다.
Disabled neutral-100 배경, 클릭 불가
Card — 정보 컨테이너

카드 타이틀

메타 라인 · 보조 정보

본문 라인 — 핵심 속성 1 / 속성 2 / 속성 3

bg: neutral-0 · border: neutral-200 · radius: lg(16) · padding: 2xl(24) · shadow: card

Modal — 다이얼로그

scrim: scrim-modal (0.6) · content radius: lg · shadow: floating · max-width: 420 · padding: 2xl

실제 코드 매핑 — React 컴포넌트

어드민 코드에서 가이드 컴포넌트를 직접 사용. 인라인 className 반복 금지 (상세: § 10.7 액션 슬롯).

가이드import 경로props 요약
Button@/components/ui/Buttonvariant=primary/secondary/ghost/destructive · size=sm/md/lg
AlertDialog (11.1)@/components/ui/AlertDialogvariant=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/InstructorPickerModalcandidates · onConfirm · children (추가 필드)
Banner (10.3)@/components/ui/Bannervariant=info/warning/danger/success · icon · children · 1px neutral-200 border
Card (10.2)@/components/ui/Cardvariant=standard/subtle/hero · className · children
PillToggle@/components/ui/PillTogglechecked · onChange · label · 활성=success-50 + ✓, 비활성=outline neutral
DLRow (10.4)@/components/ui/DLRowlabel · value · labelWidth — 정보 패널 dt/dd 한 줄
AllowanceBadge@/components/ui/AllowanceBadgename · amount · locked · onRemove — 수당 칩 (활성/잠금 분기)
PageContainer (10.1)@/components/ui/PageContainerdensity=default/compact/wide · className · children — 페이지 최상위 래퍼 (p-8 + space-y)
PageHeader@/components/ui/PageHeadertitle · description · actions slot — h1 text-ds-title
CardHeader (10.2)@/components/ui/CardHeadertitle · description · actions slot — h3 text-ds-cardTitle
FilterToolbar (10.8)@/components/ui/FilterToolbardimensions · value · onChange · search · onSearchChange · searchPlaceholder — B 패턴 (필터 칩 누적 + "+필터 추가" popover + 우측 검색). client
FilterDimension (type)@/components/ui/FilterToolbarkey · label · icon · desc · values[{value, label, optgroup}] · group · required — 페이지별 차원 정의
DataTable@/components/ui/DataTablecolumns (key · header · cell · align · width · show) · rows · rowKey · emptyMessage — generic <T>
Pagination (10.8)@/components/Paginationstate · total · buildHref · unit — URL Link 기반. 좌(범위·총합) · 중(번호) · 우(페이지당 토글)
ClientPagination (10.8)@/components/ClientPaginationpage · perPage · total · onPageChange · onPerPageChange · unit — client state 변형. 시각은 Pagination 과 동일
VisibilityBadge@/components/ui/VisibilityBadgetier=S/A/B/null — S만 / A↑ / B↑ / 전체 (brand 톤)
buttonClasses (helper)@/components/ui/button-classesvariant · size · extra — Next Link 등 비-button 엘리먼트가 가이드 Button 외관 사용. server-safe (10.7)
SegmentedControl@/components/ui/SegmentedControloptions: [{href, label, active}] · ariaLabel — 2-3 옵션 상호배타 토글 (월간/주간/일간 등)
Legend@/components/ui/Legendlabel · dotColorClass — 캘린더 색 안내 dot 범례. CHIP_DOT_COLOR 매핑 함께 사용

추가 컴포넌트 (Input · Toast 등) 는 추출 시점에 본 표 갱신.

10레이아웃 · 패턴

반복 패턴 — 박스·간격·상태.

10.1 페이지 레이아웃 · 간격 리듬

페이지 위계: Page → Section → Sub-section → Card. 각 위계마다 정해진 세로 간격을 사용한다.

Page top → 첫 Sectionsp-4xl
Section ↔ Sectionsp-3xl
Section title → 첫 카드sp-lg
카드 ↔ 카드sp-lg
Sub-section ↔ Sub-sectionsp-2xl
Card 내 영역 ↔ 영역sp-2xl
Card 내부 패딩sp-2xl
(단일 정의: § 10.2 Card 해부학)
페이지 좌우 패딩sp-3xl

페이지 컨테이너 max-width: 1280px 가운데 정렬 권장. 그 이상 넓이는 가독성 저하.

10.2 Card 해부학 — 5 영역

Card 는 5 영역의 조합. 모두 옵션 — 필요한 것만 사용. 영역 사이는 24px (sp-2xl) 분리.

HEADER제목 (cardTitle 18/700) + 우측 액션
META부제·날짜·상태 칩 (caption 13/600 neutral-500)
BODY핵심 콘텐츠 (body 15/500)
ACTIONS하단 버튼 (Primary + Secondary, 우측 정렬, gap 8)
FOOTERdivider 위 부가 정보 (caption neutral-500)

Card 변형 4종

Standard
기본. bg-neutral-0 + border + r-lg(16) + p-2xl(24) + shadow-card
Subtle
bg-neutral-50 — 추가 정보 박스, 살짝 강조 안 됨
Hero
r-2xl(28) + p-3xl(32) + shadow-md — 페이지 상단 강조
Compact
p-lg(16) — 밀집 리스트 안 카드

Don't: Card 안에 또 Card 넣지 않기 — divider 로 영역 구분. Border 와 Shadow 둘 다 쓰는 게 기본 (Standard 변형).

10.3 알림 · 배너 · 토스트

Banner — 페이지·카드 상단의 정적 알림. Toast — 화면 우상단 일시 알림 (3초). Inline message — 인풋 아래 에러 등 흐름 안.

의미bgborder (1px)text
Infoinfo-50neutral-200info-700
Warningwarning-50neutral-200warning-700
Dangerdanger-50neutral-200danger-700
Successsuccess-50neutral-200success-700

구조: padding 12px 16px · border-radius r-md(12) · border 1px neutral-200 (4면 균일) · 아이콘 + 텍스트 + 옵션 닫기.
의미는 bg 색 + 텍스트 색으로 전달. border 는 형태 분리용 neutral 톤 — 한 면만 강조하는 left-stripe 패턴 안 씀.

10.4 정보 패널 — read-only 데이터 그룹

Read-only 정보를 의미 단위로 묶어서 표현. 한 그룹은 7~8개 필드 이내. 그룹마다 emoji + 헤더 + divider, 그 아래 Label-Value 가로 페어 list.

📋그룹 A

필드 1
텍스트 값
필드 2
날짜·횟수 등
필드 3
카테고리 텍스트
필드 4
옵션 텍스트
상태
진행중
📝메모 ▼

자유 입력 필드. 그룹 안 collapsible 로 배치.

🏫그룹 B

필드 1
엔터티 이름
필드 2
위치·지역
필드 3
담당자 이름
필드 4
연락처
구성 요소사양
외곽 카드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 labelcaption (13/600 neutral-500) · 너비 96px 고정
DL Row valuebody (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 권장).

10.5 빈 상태 · 로딩 · 에러

데이터가 없거나·로딩 중이거나·실패한 상태. 화면이 비어 보이지 않도록 명시적 처리.

∅
아직 항목이 없어요
첫 항목을 등록하면 여기에 보여요.
+ 새 항목 등록
불러오는 중…
다시 시도
상태구성패딩
Empty아이콘(48) + Title (cardTitle) + Subtitle (body neutral-500) + Action32px+, 가운데 정렬
Loading < 1초Spinner small (16~24px)flow 안에 inline
Loading 1~3초Skeleton (실제 콘텐츠 형태 모방)실제 카드 위치 유지
Loading 3초+Skeleton + "로딩 중..." 텍스트—
ErrorDanger banner (10.3) + 재시도 버튼—

Don't: "데이터 없음" 만 적기 (다음 액션 제시 필요) · 무한 회전 스피너 (스켈레톤 권장) · Stack trace 그대로 노출.

10.6 Form 패턴

입력이 필요한 화면 (등록·수정·설정) 의 표준 패턴. Form Field · Form Section · Required marker · Inline validation 4가지를 일관되게 적용.

기본 정보
입력 형식 안내 (helper 텍스트)
⚠이 항목을 입력해주세요.
선행 필드를 먼저 입력해주세요.

구성 사양

요소스펙
Form SectionStandard Card (r-lg, p-2xl, shadow-card) · 헤더 16/700 + border-b divider · 내부 form-grid
Form Section title16/700 neutral-900 · padding-bottom 8 · border-b 1px neutral-200 · margin-bottom 16
Form Section ↔ Sectionsp-3xl (32px) 세로 간격
Form Grid2 column · gap-x sp-lg(16) · gap-y sp-md(12)
Form Fieldlabel + input + (helper/error) 세로 stack · gap-y 6px
Form Labelcaption (13/600 neutral-700) — 필드 라벨이라 약간 진한 톤
Required marker* · 빨간 색 (danger-500) · 라벨 끝에 4px 띄움 · font-bold
Form Inputheight 36 (sm) 또는 40 (md) · padding x12 · r-sm(8) · border neutral-200 · focus border brand-500 + ring 3px brand/15
Form Textareamin-height 72 · padding 8x12 · resize vertical
Form Helper textmicro (11/500 neutral-500) · input 아래
Form Error messagemicro (11/500 danger-700) · input 아래 (helper 와 같은 자리, 등장 시 helper 대체) · ⚠ 아이콘 prefix 권장
Error 시 Inputborder danger-500 · bg danger-50 (살짝 핑크) · focus 시 ring 도 danger 톤
Disabled 시 Inputbg 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 가 명확해야 함

10.6.1 Currency Input — 통합 (Canonical)

✅ CANONICAL

A 배경 + C 폰트 혼합

흰 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 · 우측 정렬 · 콤마 자동
원 suffix14px / 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).

10.6.2 Date Input

날짜 입력은 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 추출 예정).

10.6.3 Phone Input

연락처는 마스킹 자동 패턴 사용. 사용자는 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자리를 입력해주세요")
placeholder010-1234-5678

적용 자리: SignUp 연락처, MyInfoScreen 본인 연락처, InstructorProfileScreen 강사 연락처 입력, 향후 모든 휴대폰 번호 입력. 컴포넌트는 FormFields::PhoneInput primitive + PhoneInputField (라벨 동반 wrapper).

10.7 액션 슬롯 — 통합 룰

페이지·카드·모달 어디든 "액션 영역" 은 동일한 룰을 따른다. 위치(컨테이너)는 슬롯마다 다르지만, 내부 레이아웃·정렬 우선순위·오버플로 규칙은 5자리 모두 동일. 슬롯마다 따로 결정하면 정합성이 깨진다.

5 자리 인벤토리

#슬롯컨테이너정렬예시
①PageHeader actions페이지 최상단 우측justify-between (좌측 title 과 한 행)+ 새 항목 / 편집
②CardHeader actionsCard 영역 상단 우측justify-between (좌측 title 과 한 행)+ 항목 추가 / 편집
③Card 하단 ACTIONSCard 영역 마지막 줄justify-end (우측 끝)저장 · 취소 / 삭제
④Modal·AlertDialog 푸터다이얼로그 하단justify-end (우측 끝, destructive 좌측은 예외)취소 / 실행
⑤Page bottom actionsPageContainer 마지막justify-end (우측 끝)등록 · 수정 저장 (Form)

내부 레이아웃 — 5 자리 공통

flex items-center gap-2

단일 액션  → 그대로
주요 + 보조 1쌍 → [Secondary] [Primary]   (Primary 가 오른쪽 끝)
3개 이상       → … 드롭다운 메뉴로 묶음
destructive 포함 → 다른 액션과 거리 (gap-3) 또는 별도 그룹

버튼 사이즈 — 슬롯별 매핑

슬롯기본 사이즈비고
① ② ③ ⑤md (h-10)일반적인 페이지·카드·폼 모두 동일
④ Modalmd (h-10)좁은 모바일 모달 (max-w-sm) 만 sm (h-8) 허용
밀집 리스트 안 rowsm (h-8)OfferAssignments 같은 inline action 한정

오버플로 — 좌측·우측 분담

  • 좌측 title 영역: flex-1 min-w-0 + 길면 truncate
  • 우측 액션 영역: flex-shrink-0 + whitespace-nowrap (절대 줄바꿈 금지)
  • 모바일 좁아질 때: ② ③ 만 wrap 허용. ① ④ ⑤ 는 wrap 금지 — 페이지·모달 헤더는 깔끔 유지

컴포넌트 매핑 — 인라인 className 금지

슬롯모바일 (RN)어드민 (Next)
① PageHeader.actionsPageHeader.tsxcomponents/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 흩어짐의 시작.

Don't (전체 룰: § 운영 룰)

  • 한 슬롯에 같은 variant 2개 (Primary 둘 두지 말 것 — 결정점 흐려짐)
  • 액션 4개 이상 — 드롭다운 강제. 4개 모두 펴두지 말 것
  • destructive 를 Primary 위치 (오른쪽 끝) 에 두기 — 실수 유발
  • Cancel 류 ghost 버튼을 우측 끝 — Primary 가 가장 오른쪽이어야 액션 무게 일관
  • 인라인 className="bg-... border-... rounded-..." 로 가이드 Button 외관만 흉내 — 외관 유사해도 사이즈·variant 시스템 안 따름
10.8 리스트 페이지 — 검색·필터·페이지네이션 (B 패턴)

/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        표 하단 — [범위·총합] [페이지 번호] [페이지당 토글]

FilterToolbar — 검색·필터 통합 row

옛 FilterBar (form 래퍼 + select 들) 를 대체하는 클라이언트 컴포넌트. 각 필터 차원을 칩으로 표현하고 popover 로 값을 바꾼다.

  • 필터 칩 — 적용 중인 필터만 표시. 칩 클릭 시 값 popover (radio 선택). × 버튼으로 제거
  • +필터 추가 popover — 점선 border 버튼. 클릭 시 차원 목록 popover (검색 가능, 그룹 헤딩 "필터 차원" / "빠른 필터"). 이미 적용된 차원은 비활성
  • 검색 input — 우측 (flex-1 으로 밀어냄), w-56 h-8, 🔍 아이콘. placeholder 에 검색 범위 명시 ("학교·지역 검색")
  • 검색 동작 — 300ms debounce 후 URL ?q= 자동 반영. 별도 제출 버튼 없음 (Enter 도 불필요)
  • URL 동기화 — 모든 필터 변경 시 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=201-based · 다른 쿼리(?chip=, ?q= 등) 와 함께 보존
쿼리 검증옵션 외 값은 default(20)parsePagination() 에서 흡수

Pagination — 표 하단 통일 (좌·중·우 3분할)

위치: DataTable 다음 줄, 표 하단 중앙. 검색·필터 영역과 분리 — 같은 row 에 섞지 않는다.

  • 좌 — 범위·총합 (1–20 / 163건). 굵게 강조된 범위 + 회색 총합
  • 중 — 페이지 번호 ‹ 1 2 3 … 9 ›. 7페이지 이하면 그대로 다 표시, 초과면 양 끝 + 현재 주변만 (pageRange() 헬퍼)
  • 현재 페이지 = bg-neutral-900 text-neutral-0 굵게. 비활성 = text-neutral-300
  • 우 — 페이지당 토글 (20 / 50 / 100). 활성 = bg-neutral-900 text-neutral-0
  • 현재 페이지가 총 페이지 초과 시 자동 보정 (clampPage())
  • 빈 결과 (total === 0) 시: Pagination 자체 비표시 (return null). DataTable empty row 가 안내
  • 1 페이지뿐이면 (max ≤ 1) 중앙 번호 영역 비움 — 좌측 카운트 + 우측 페이지당 토글만

두 가지 Pagination — URL 기반 vs 클라이언트 상태

컴포넌트import사용처
Pagination (URL)@/components/PaginationSSR 페이지 — ?page=&per_page= Link 기반. buildHref 로 페이지 외 쿼리 보존. 기본 선택지.
ClientPagination@/components/ClientPaginationclient state 로 데이터 가공하는 페이지 (예: CompleteBatchTable). props: page · perPage · total · onPageChange · onPerPageChange. 시각 디자인은 동일.

Don't (전체 룰: § 운영 룰)

  • 옛 FilterBar, FilterChip, FilterChipGroup 사용 — 삭제된 컴포넌트. 새 페이지는 FilterToolbar
  • Pagination 을 표 위에 두기 — 검색·필터 영역과 시각 충돌. 표 하단 통일
  • 검색·필터·페이지네이션을 한 row 에 섞기 — 시각 noise. 검색·필터 row 와 페이지네이션 row 분리
  • 페이지 사이즈를 [20, 50, 100] 외로 (예: 25, 30, 200)
  • 무한 스크롤로 갈아 끼우기 — 어드민은 비교·운영 화면이라 페이지 단위. 모바일은 FlatList(무한) 별개
  • 검색에 별도 제출 버튼 ("검색" / Enter) 강요 — 300ms debounce 자동 검색이 표준 (URL 동기화는 코드 안에서 처리)
  • 필터 칩에 적용되지 않은 차원도 다 노출 — 칩은 "적용 중" 만 표시. 추가는 "+필터 추가" popover
  • FilterToolbar / Pagination 위치 페이지마다 다르게 — 사용자 학습 비용

11Alert · Badge · Push

Alert · Badge · Push. Banner/Toast (§ 10.3) 와 구분.

11.1 Alert · Confirmation Dialog

Modal (9. 컴포넌트) 의 변형 — 사용자 확인이 필요한 경우만 사용. 일반 폼·복잡한 UI 는 Modal 을 그대로 쓰고, "정말 하시겠어요?" 류는 Alert.

▲
단계를 진행하시겠어요?
다음 상태로 넘어가는 영향 범위를 1~2 줄로 안내. 일반적인 확인 액션.
⚠
되돌릴 수 없는 작업이에요
파괴적 결과 (삭제·취소·공개 철회) 의 효과를 명시. Primary 버튼은 danger-500.
⚠
저장하지 않고 나가시겠어요?
작성 중인 내용이 사라져요.
유형아이콘Primary 버튼예시
Confirmationinfo-50/700 (▲)brand-500완료확인 처리
Destructivedanger-50/700 (⚠)danger-500공고 취소·삭제
Warningwarning-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 는 항상 명시적 액션 필요).

11.2 Badge · Dot — 앱 내 시그널

"여기에 안 본 게 있어요" 를 알리는 시각 마커. 3 종.

🔔
Dot
안 읽음 시그널 (개수 무관)
🔔 3
Count Badge
개수 표시 (1~99, 100+ 는 "99+")
🔔 99+
Count 99+
100건 이상
⚙ NEW
NEW Badge
신규 기능·항목 강조
종류크기색위치
Dot10×10px (border 2px neutral-0 포함)danger-500아이콘 우상단 (-2, -2)
Countmin 18px, h 18, padding 0 5danger-500 + neutral-0아이콘 우상단 (-6, -8)
NEWauto, padding 2 6brand-500 + neutral-0요소 우상단

위치 룰: 아이콘 위 → 우상단 / 메뉴 항목 옆 → 우측 / 탭바 아이콘 위 → 우상단.
Don't: 모든 항목에 Dot (신호 가치 사라짐) · 다양한 색 사용 (warning/info dot 등 — 한 의미로 통일) · 큰 dot 으로 시각 압도.

11.3 Push Notification — Phase 2 도입 예정
🚧 Phase 2 — 정식 오픈 전 도입

현재 미구현. 정식 오픈 직전에 추가될 예정.

아래는 도입 시 결정해야 할 항목 골격. 실제 룰은 도입 시점에 본 가이드에 추가.

알림 카테고리 후보

카테고리대상중요도예시
지원 수락강사일반"○○중학교 해석연수에 배정됐어요"
재배정강사중요"○○중학교 배정이 취소되었어요 (사유: 일정 변경)"
마감 임박HQ일반"○○공고 D-1, 지원자 0명"
완료확인 대기HQ일반"○○공고 교육 종료 — 완료확인 처리 필요"
지급처리 완료강사일반"○○ 강의 지급처리됐어요"
시스템 공지전체중요"이용약관 변경 안내"

도입 시 결정 항목

  • 채널: Expo Push → APNs (iOS) / FCM (Android)
  • 권한 요청 시점: 회원가입 직후 vs 첫 알림 발송 직전
  • 알림음·진동: 카테고리별 차등 (중요만 진동) vs 일률
  • 페이로드 구조: title / body / data (postId, type, etc.)
  • 앱 내 노티 센터 연동: Push 도착 시 앱 내 Badge·Dot 동시 갱신
  • 읽음 처리: 알림 탭 → Badge dismiss + paid_at 같은 read flag 갱신
  • 설정 화면: 사용자가 카테고리별 on/off 가능

참고: 11.2 의 Badge/Dot 은 Push 와 무관하게 앱 내에서 작동. Push 도입 후엔 두 시스템이 서로 동기화 (push 도착 → in-app dot 켜짐).

11.4 ActionSheet — multi-option 모달

Alert (11.1) 의 multi-option 변형. 3개 이상 옵션 중 하나 선택이 필요할 때만 사용. RN Alert.alert 의 OS 기본 multi-button 알럿을 브랜드 톤으로 대체. 옵션이 2개 이하면 Alert (11.1) 의 [Cancel] [Primary] 패턴이 더 자연스럽다 — ActionSheet 는 진짜 옵션 분기일 때만.

요소스펙비고
scrimscrim-modal (0.6)Alert 와 동일
contentr-lg · neutral-0 · elevation.floatingAlert 와 동일
max-width320px모바일 좁은 화면 기준 — Alert 의 420 보다 작음
paddingspace-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 는 마지막 또는 별도 표시).

12Overlay · Scrim

모달·드로어·드롭다운 뒤쪽 배경. 토큰만 사용.

rgba(15,23,42,0.6)
모달, 풀스크린
scrim-light · 0.3
rgba(15,23,42,0.3)
드롭다운, 툴팁
토큰Tailwind 클래스용도
scrim-modalbg-scrim-modal모달, 풀스크린 다이얼로그
scrim-lightbg-scrim-light드롭다운, 툴팁 배경

Quality · 품질 가드레일

다크모드·모바일 특수·접근성·아이콘·Voice & Tone — 출시 전 점검 항목.

13Dark Mode 매핑

다크모드 토큰 페어 (미구현, 레퍼런스).

역할라이트다크
페이지 배경neutral-50 #F8FAFCneutral-900 #0F172A
카드 / 모달neutral-0 #FFFFFFslate-800 #1E293B
보더neutral-200 #E2E8F0neutral-700 #334155
본문 텍스트neutral-900 #0F172Aneutral-100 #F1F5F9
보조 텍스트neutral-500 #64748Bslate-400 #94A3B8
Brand (CTA)brand-500 #2F80EDblue-400 #60A5FA
다크모드 카드 미리보기

메트릭 A

3건

메트릭 B

₩000,000

기본 액션

14모바일 특수

RN 앱 특화 룰 — Touch target · Safe area.

Touch Target — 최소 탭 영역
×
32 × 32
⚠ 너무 작음
↻
44 × 44
iOS 최소 (HIG)
+
48 × 48
✓ 권장 (Material)
▶
56 × 56
FAB, 주요 액션

룰: 작은 아이콘이라도 패딩으로 감싸 최소 44, 권장 48 의 탭 영역을 확보. 32 이하는 사용 금지.

Safe Area — 노치 / 홈바

모든 화면 컨테이너는 SafeAreaView 또는 useSafeAreaInsets 를 사용. 상단·하단 시스템 영역 침범 금지.

위치iPhone (노치)iPhone (홈바)Android (3-button)
상단 inset44~59px—24px (status bar)
하단 inset—34px0~48px
플로팅 BottomTabBar

모바일 모든 화면 하단의 표준 탭바. 좌·우·아래 띄워 둥근 알약 모양으로 배치 — 콘텐츠 위에 떠 있는 형태.

속성값 (토큰)비고
위치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-fileOnMinergba(255,255,255,0.12)본인 메시지(brand 배경) 위 첨부 박스 강조 — 흰 12% 반투명

말풍선·tooltip·system message 등 나머지 채팅 토큰은 docs/chat-design.md 참조.

15Accessibility (a11y)

WCAG 2.1 AA. 색·키보드·스크린리더.

15.1 색 대비 (Color Contrast) — WCAG AA

본문 4.5:1 이상 · 큰 글자 (18pt+ or 14pt bold) 3:1 이상 · UI 컴포넌트·아이콘 3:1 이상.

토큰 조합대비비상태
text-neutral-900 on neutral-016.8:1✓ AAA
text-neutral-700 on neutral-010.4:1✓ AAA
text-neutral-500 on neutral-05.2:1✓ AA
text-brand-700 on neutral-07.5:1✓ AAA
text-brand-500 on neutral-04.6:1✓ AA (간신히)
text-info-700 on neutral-07.5:1✓ AAA
text-success-700 on neutral-04.7:1✓ AA
text-warning-700 on neutral-05.2:1✓ AA
text-danger-700 on neutral-05.9:1✓ AA
text-cal-sun-600 on neutral-05.0:1✓ AA
text-cal-sat-600 on neutral-04.6:1✓ AA (간신히 — 본문 한정. 굵게 권장)

신규 토큰 추가 시 WebAIM Contrast Checker 로 검증 후 본 표 갱신.

15.2 키보드 네비게이션
  • 모든 인터랙티브 요소 (button·link·input·select) Tab focusable
  • Tab 순서 = 시각 흐름 (좌→우, 상→하). DOM 순서 = 시각 순서 유지 (CSS order·flex-direction reverse 신중)
  • 모달 안 focus trap — 첫 Tab 이 모달 첫 요소, 마지막 Tab 이 모달 첫 요소로 순환
  • Esc 로 모달 닫기 + focus 복귀 (트리거 button 으로)
  • Enter/Space 로 button 활성화 (native button 사용 시 자동)
15.3 Focus Indicator

focus:outline-none 단독 사용 금지. 반드시 시각 대체 (ring·shadow·border) 동반.

요소표준 focus 표현
Input · Select · Textareafocus: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 + 대체 없음. 키보드 사용자가 현재 위치 모름.

15.4 ARIA 룰 — 패턴별 표준 속성
패턴필수 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 더보기 토글
15.5 색맹·시각 보강 — Color is not the only signal

색만으로 의미 전달 금지. 텍스트 라벨·아이콘·패턴 병행 필수.

자리색 단독 위반 가능성병행 보강
StatusBadge (단계 칩)색만 다름 (info/warning/success)라벨 텍스트 (모집중/교육중/완료확인) 항상 표시 ✓
Form 에러border-danger-500 만으로 부족error 메시지 인라인 + ⚠ 아이콘 권장
캘린더 토·일 색cal-sun/sat 만 다름요일명 라벨 (일/토) 병행 ✓
정산 상태 (확정/취소/초안)text 색만 다름라벨 텍스트 + 취소는 line-through ✓
15.6 Don't (전체 룰: § 운영 룰)
  • color: red 만으로 에러 표시 — 텍스트·아이콘 병행
  • outline:none 단독 + 대체 없음 — 키보드 사용자 위치 상실
  • 모달에 role="dialog" 없음 — 스크린리더 컨텍스트 미인식
  • 아이콘 단독 button 에 aria-label 없음 — 스크린리더는 빈 button 으로 인식
  • 토큰 추가 시 색 대비 검증 누락 — § 15.1 표 갱신 필수

16Iconography

아이콘 라이브러리 · 사이즈 · 색 · 사용 패턴. lucide-react 표준.

16.1 라이브러리 — 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 지원커스텀 아이콘 추가 시 별도✓
heroiconsTailwind 팀 · 깔끔아이콘 수 적음 (300+)
phosphor-iconsweight 6종 (thin~bold)패키지 큼
이모지·텍스트 기호설치 0플랫폼별 렌더 다름·접근성 약함❌
16.2 사이즈 토큰 — 4단계
토큰크기용도예시
icon-xs12×12인라인 텍스트 옆 (caption)locked 🔒, info ⓘ
icon-sm16×16버튼 안·메뉴 항목+ 추가, ← 이전
icon-md20×20탭바·툴바 (모바일)홈·검색·프로필
icon-lg24×24FAB·페이지 헤더 액션편집·설정

Don't: 18, 22, 28 같은 임의 값 — 4 단계 외 사용 금지. 디자이너 제공 SVG 도 토큰 사이즈로 export.

16.3 색 — currentColor 또는 의미 토큰

기본 currentColor (텍스트 색 따라감) 사용. 의미 강조 시에만 v2 토큰 명시.

상황색예시
일반 (텍스트와 일체)currentColor버튼 안 + 아이콘, 메뉴 항목
의미 강조 — 성공text-success-700저장 완료, 배정 완료
의미 강조 — 경고text-warning-700마감 임박, 미입력 알림
의미 강조 — 위험text-danger-700삭제 버튼, 에러 인디케이터
비활성·보조text-neutral-500읽음 표시, 부가 정보
16.4 사용 패턴
패턴구조접근성
아이콘 + 텍스트 <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="로딩 중"
16.5 현재 어드민 자리 → lucide 매핑

코드에 박혀있는 이모지·텍스트 기호 → lucide 컴포넌트 교체 매핑.

현재lucide위치
🔒LockAllowanceBadge · ObserverSection 잠금 표시
⚠AlertTrianglemaster 배너 · PostForm lockSchedule · AlertDialog warning
✓CheckPillToggle 활성 · CompletePostButton · AlertDialog success
←ChevronLeft캘린더 이전 (education-schedule, schedule)
→ChevronRight캘린더 다음 · PostForm 시간 표시
+ (텍스트)Plus+ 새 항목 · + 부속 추가
×X모달 닫기 · 칩 삭제
16.6 Don't (전체 룰: § 운영 룰)
  • 이모지 (🔒/⚠/✓) 직접 사용 — 플랫폼별 렌더 다름·다국어 부적절·접근성 약함
  • 텍스트 화살표 (←/→) 직접 사용 — 폰트별 모양 다름. ChevronLeft 등 사용
  • 임의 사이즈 (18, 22, 28) — 4 단계 토큰만
  • 색 직접 박기 — currentColor 또는 v2 토큰만
  • 아이콘 단독 button 에 aria-label 누락 — § 15.4 강제

17Voice & Tone

버튼 라벨 · empty state · error · success 카피 표준.

17.1 톤 원칙 — 4가지
원칙예시 (○ 권장)예시 (✕ 회피)
명료 > 격식 "공고가 등록됐어요" "공고 등록이 완료되었습니다." (딱딱)
능동 > 수동 "강사 1명을 배정하세요" "강사가 배정되어야 합니다" (수동)
결과 > 절차 "저장됨" "저장 처리가 완료되었습니다" (장황)
비난 > 안내 "이메일 형식이 맞지 않아요" "잘못된 입력입니다" (비난)
17.2 Button 라벨 — 자리별 표준

"취소"/"닫기"/"뒤로"/"확인" 혼용 금지. 다음 표대로 통일.

자리왼쪽 (보조)오른쪽 (주요)
일반 modal (정보 입력)취소저장 / 등록 / 추가
확인 modal (위험 액션)아니오삭제 / 취소 / 확정 (구체 동사)
알림 modal (단순 OK)—확인
인라인 폼 (펼침)뒤로적용 / 추가 (구체 동사)
읽기 전용 modal—닫기
드로어·시트—닫기 (X 아이콘 + aria-label)

구체 동사 우선: "확인" 대신 "삭제"·"저장"·"제출" 같이 결과를 명시. 사용자가 "확인" 누른 다음 무엇이 일어날지 모르면 안 됨.

17.3 Empty State 문구

비어있는 자리에는 현재 상태 + 다음 행동 힌트 두 줄. "—" 만 쓰지 말 것.

자리현재 상태 (1줄)다음 행동 (2줄, 선택)
리스트 0건아직 등록된 [항목] 이 없어요+ 새 [항목] 등록 버튼 안내
필터 결과 0건해당하는 [항목] 이 없어요"필터 초기화" 링크 동반
완료 후 (성공)모두 처리됐어요—
권한 없음이 페이지 접근 권한이 없어요"홈으로" 또는 관리자 문의
17.4 Error 메시지
유형패턴예시
입력 검증 "[필드] 가 [조건] 이에요" "이메일 형식이 맞지 않아요" / "비밀번호는 8자 이상이어야 해요"
네트워크·서버 "[행동] 못 했어요. [재시도 안내]" "저장 못 했어요. 다시 시도해주세요"
권한 "[행동] 권한이 없어요" "이 공고는 작성자만 수정할 수 있어요"
제약 위반 "[조건] 이라서 [결과] 못 해요" "이미 배정된 강사라서 취소할 수 없어요"

금지: "에러 발생" / "실패했습니다" / "Unknown error" 같은 무의미 메시지. 항상 왜 + 무엇 명시.

17.5 Success / Loading 메시지
상태패턴예시
저장 완료 (즉시)"[항목] [동사]됐어요""공고가 저장됐어요"
저장 완료 (인라인 표시)"저장됨" (간결)FormField 옆 작은 텍스트
로딩 (버튼)"[동사] 중…""저장 중…" / "처리 중…"
로딩 (페이지)"[항목] 불러오는 중…""공고 목록 불러오는 중…"
17.6 어조 — 시제·존댓말
  • "~해요" 체 기본 (격식 ↓ 친근 ↑) — 토스·당근 표준
  • 완료 → 과거형 "됐어요" (현재완료 어색)
  • 오류 → 현재형 "안 돼요" / "못 해요"
  • 금지: "~하십시오" (군대 톤) · "~합니다" (격식 과함) · "~함" (반말)
17.7 Don't (전체 룰: § 운영 룰)
  • "확인" 만으로 끝나는 confirm 버튼 — 결과 동사 (삭제·저장·제출) 명시
  • "취소"/"닫기"/"뒤로" 자리별 룰 무시 — § 17.2 표 따름
  • Empty state 에 "—" 만 표시 — 현재 상태 + 다음 행동 힌트 동반
  • "에러 발생" 같은 무의미 에러 — 왜 + 무엇 명시
  • 한 화면에 "~해요" 와 "~합니다" 혼용 — 한 톤으로 통일

Meta · 운영

ADR (결정 기록) + 운영 룰 12조항. 변경 거버넌스의 단일 진입점.

18ADR — Architecture Decision Records

핵심 결정의 왜·근거 기록 + 외부 벤치마크.

ADR-01 · Brand color = #2F80ED
상태Accepted (2024-Q4)
결정brand-500 = #2F80ED (밝은 파랑, Tailwind blue-500 과 비슷)
맥락처음 shadcn 템플릿의 무채색 (검정 primary) 에서 출발 → 학습·업무 도구라 신뢰감 + 차분함 필요
근거① 파랑 = "신뢰·전문성" 보편 의미 (한국 SaaS 표준: 토스·카카오워크·노션 모두 파랑)
② 채도 60% 라 장시간 작업 피로 적음
③ Semantic info 와 hex 공유 — 정보성 강조에 자연
대안녹색 (성공 색과 충돌) · 보라 (특이하지만 SaaS 어색) · 회색 단색 (개성 부족)
벤치마크토스 #3182F6 (비슷) · Polaris #008060 (녹색·차별화) · Material #6750A4 (보라)
ADR-02 · Typography 8단계 스케일
상태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)
ADR-03 · Spacing 4-base 토큰 (xs~5xl)
상태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
ADR-04 · Button variant 4개
상태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)
ADR-05 · v1 (shadcn HSL 변수) → v2 직접 hex 마이그레이션
상태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 추가. 형식:

  • 상태: Proposed / Accepted / Superseded by ADR-N
  • 결정: 한 줄 요약
  • 맥락: 왜 결정 필요했나 (배경)
  • 근거: 왜 이 선택인가 (장점·트레이드오프)
  • 대안: 검토했지만 채택 안 한 옵션
  • 벤치마크: 외부 시스템 비교 (Material·Polaris·HIG·토스 등)

참고: ADR 패턴은 Michael Nygard 의 ADR template 따름. 소프트웨어 아키텍처 문서 표준.

📋 운영 룰 — 4 그룹 / 12 조항

가이드 전반에 흩어진 Don't 의 단일 진입점. 새 페이지·컴포넌트 작성 시 본 룰 위반 여부 먼저 검증.

Color (1–6)

  1. 토큰 외 hex 직접 사용 금지 — 새 색이 필요하면 토큰을 추가하고 가이드를 갱신. '#xxxxxx' 직접 정의 금지.
  2. Category 와 Semantic 의 hex 절대 공유 금지 — 사용자가 의미를 혼동.
  3. Semantic 한 화면 동시 2종까지 — 3종 이상이면 색 신호 자체가 무의미.
  4. Neutral 은 의미를 담지 않는다 — 회색이 "위험·성공" 역할 금지.
  5. Category 는 chip 한 곳에서만 — 본문·보더·큰 배경에 카테고리 색 금지.
  6. 500 → 600 → 700 의 의미 분리 — 500 기본 / 600 hover·pressed / 700 텍스트 강조.

Typography & Spacing (7–8)

  1. 임의 폰트 크기 사용 금지 — 8단계 타입 스케일 외 값 금지.
  2. 임의 픽셀 간격 금지 — 4의 배수 spacing 토큰만 사용.

Component & Interaction (9–11)

  1. 임의 z-index 숫자 금지 — z-base/sticky/overlay/modal/toast 5 단계만.
  2. 임의 transition 시간 금지 — duration 토큰 (fast/base/slow/slower) 만.
  3. 한 화면에 Primary 버튼 1개 — 가장 중요한 액션 하나만 강조.

Mobile (12)

  1. Touch target 최소 44 / 권장 48 — 모바일에서 작은 아이콘도 패딩으로 감쌈.

컴포넌트·패턴별 Don't 색인 (Quick Reference)

위 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