# Runtime Theme 기반 Design System 프로젝트 지침 (개정안) > 상태: 현재 하나의 Git Repository에서 UI/CENTER npm workspaces의 compiled dist build/export, JSON 컬러 원본, Global/Scoped Runtime Apply·URL Loader, 통합 Storybook/Designer JSON Preview, Theme-only artifact를 관리한다. Registry publish 이력은 테스트 ZIP 검증 범위 밖이며 실제 CDN/API/Publish pipeline은 미구현이다. 현재 구현의 운영·배포 절차는 [Theme delivery](theme-delivery.md)를 따른다. > **2026-10-07 사용자 확정 정책은 개정안 상태와 관계없이 필수 적용한다.** AI의 모든 신규·수정 UI는 사용자의 별도 지시 없이 기존 Component / Storybook Reference를 먼저 확인하고 Theme / Semantic Token과 단일 Navigation Mode를 기본 적용하며 Custom은 최소화한다. 필수 요약은 [AGENTS.md](../../AGENTS.md), 상세 구현 정책은 [shadcn 기반 구현 원칙](shadcn-기반-구현-원칙.md), Theme 정책은 [테마 토큰 가이드](테마-토큰-가이드.md), Navigation 정책은 [Navigation Single Mode Policy](navigation-single-mode.md)가 각각 담당한다. ## 1. 목적과 결정 사내 공통 UI는 shadcn을 **구현 기반이자 upstream 참고원**으로 사용한다. Design System 저장소에서 가져온 코드를 회사 규칙에 맞게 관리하고 `@company/ui`로 배포한다. 서비스는 shadcn 코드를 직접 가져오거나 수정하지 않고 공통 패키지를 사용한다. 컴포넌트의 구조·동작·기본 스타일 규칙은 패키지 버전으로, 운영 중 바꿀 수 있는 디자인 값은 CSS Custom Properties 기반 Runtime Theme으로 관리한다. **소스와 검수 자료는 하나의 Git 저장소에서 관리하되, 배포 산출물은 `@company/ui` 패키지와 Theme 버전으로 나눈다.** Storybook은 같은 저장소의 실제 컴포넌트로 내부 검수, Theme Preview, 문서화를 수행한다. 서비스가 소비하는 배포물은 패키지와 Theme 데이터이며, Storybook 자체가 배포 API는 아니다. | 변경 경계 | 소유 대상 | 반영 경로 | |---|---|---| | 컴포넌트 코드/API·기본 CSS 규칙 | DOM, variant, state, interaction, 접근성, 구조적 layout, selector와 기본 스타일 규칙 | Design System 변경 → `@company/ui` 릴리스 → 앱 의존성 업데이트 및 배포 | | Runtime Theme 값 | 사전에 허용한 Semantic Token의 값 | Preview → 검증/승인 → 불변 Theme 버전 Publish → API/CDN → 앱 재방문·새로고침 시 적용 | | Theme 계약 | Token 이름·타입·의미, 스키마, 허용 범위 | Design System 코드 변경·릴리스, 호환성 확인 후 Theme 발행 | **이미 지원하는 Semantic Token 값만 변경할 때** Theme Publish 후 앱 재배포 없이 반영할 수 있다. Git에 CSS/JSON을 수정·커밋하는 행위만으로 운영 앱이 자동 갱신되지는 않는다. Theme Loader와 API/CDN 연결, 검증·승인·Publish가 먼저 구축되어야 한다. 새로운 Token, selector, 컴포넌트 API가 필요한 변경은 값 Publish로 처리할 수 없다. ## 2. 아키텍처와 소유권 ```text shadcn upstream → HM Design System 단일 Git 저장소 ├─ 컴포넌트·기본 CSS ── 패키지 릴리스(@company/ui) ──→ 개발팀 앱 업데이트·배포 ├─ Semantic Theme 초안 ── 검증·승인·Publish ──────→ Theme API/CDN └─ Storybook ── 실제 컴포넌트·Theme Preview │ ↓ 앱 Theme Loader → CSS 변수 적용 ``` - **디자인팀 — Design System**: Token 의미와 값, 컴포넌트 디자인, shadcn 도입·업스트림 변경 검토, 컴포넌트 구현/API와 접근성, Semantic Token 계약, 기본 Theme, `@company/ui` 패키지 릴리스, Storybook 및 Preview 검수를 담당한다. - **디자인팀 — Theme 운영**: Theme 값 검증, 불변 버전 저장, 승인·Publish·롤백, API/CDN 제공, 캐시 및 관측을 담당한다. MVP에서는 수동 절차로 시작할 수 있다. - **개발팀**: `@company/ui`와 기본 CSS 사용, 서비스 앱의 Theme 식별·로딩·적용·실패 대응, 패키지 업데이트 및 앱 릴리스를 담당한다. 위 항목은 조직 분리가 아니라 작업 단계와 책임의 구분이다. 같은 담당자가 여러 단계를 수행해도 각 산출물과 승인·반영 기록은 남긴다. ## 3. Token 계약 ```text Primitive (설계·빌드 시 관리) → Semantic (컴포넌트 참조) → Component ``` 컴포넌트는 `blue-600` 같은 Primitive나 `#2563eb`, `8px` 같은 실제 값을 직접 참조하지 않는다. Tailwind를 쓰는 경우에도 `bg-primary`의 `--color-primary` → `--primary` → `--hm-sem-action-primary` adapter 연결을 확인한다. **Runtime override는 공개 allowlist의 Semantic/Theme palette 값만** 허용한다. Primitive, 컴포넌트 전용 내부 변수, 임의 CSS selector/rule/JavaScript는 Publish 대상이 아니다. 현재 allowlist는 `packages/ui/src/theme/contract.ts`의 83개 입력이다(색상 Semantic/Theme palette와 input/container radius). 정확한 이름·타입·기본값·허용 범위는 이 계약과 `runtime-theme-contract.md`를 따른다. Shadow geometry는 현재 공개 입력이 아니다. Typography와 spacing은 실제 사용처와 레이아웃 영향 검토 후 개별 등록한다. 문서에 예시가 있다는 이유로 곧바로 Runtime 허용 Token이 되지 않는다. 기본 Theme은 `@company/ui`가 배포하는 CSS에 포함하고 앱이 초기 렌더 전에 로드한다. Runtime 값이 없거나 가져오기·검증에 실패해도 기본값으로 UI가 렌더링되어야 한다. 현재 Light/Dark 구분은 없으며 단일 HM Theme 값을 기준으로 한다([테마 토큰 가이드](테마-토큰-가이드.md) 참조). ## 4. 변경 분류 | 요청 예 | 처리 | 판단 기준 | |---|---|---| | 기존 `color.action.primary` 값 변경 | Theme Publish | 계약에 등록된 Semantic Token의 값만 변경 | | 기존 공개 radius 값 변경 | Theme Publish(운영 도입 후) | 허용 범위·레이아웃·접근성 검증 통과; shadow geometry는 현재 계약 밖 | | 새 색상 Token 이름 또는 의미 추가 | 패키지/계약 릴리스 후 Theme Publish | 구버전 앱의 호환성 확인 필요 | | Button DOM, icon slot, variant, loading state 변경 | 패키지 릴리스 + 앱 업데이트 | API·구조·동작 변경 | | Dialog 상호작용, 포커스, 접근성 변경 | 패키지 릴리스 + 앱 업데이트 | 실행 코드 변경 | | grid/flex 배치, display/position, animation logic 변경 | 패키지 또는 앱 코드 배포 | 디자인 값만으로 안전하게 표현할 수 없음 | | 앱 화면의 콘텐츠·업무 로직 변경 | 앱 배포 | Design System 바깥의 책임 | 애매한 요청은 디자인팀과 개발팀이 영향 범위를 먼저 분류한다. Semantic Token이라는 이름만 붙여 레이아웃 구조나 동작을 Runtime으로 우회하지 않는다. ## 5. 단일 Git Repository와 산출물 구조 `HMDesignSystem`을 **하나의 Git 저장소**로 관리한다. 저장소 수와 배포물 수는 다르다. 동일 저장소의 변경이라도 컴포넌트·기본 CSS는 패키지 릴리스, 기존 Semantic Token 값은 Theme Publish, Storybook은 내부 검수용 빌드로 각각 처리한다. 개발팀의 서비스 앱은 이 저장소의 소스 파일을 직접 복사하지 않고 배포된 패키지와 Theme API/CDN을 소비한다. 현재 파일 배치는 다음과 같다. Library dist 산출물과 Runtime URL Loader는 구현되어 있다. Registry publish 이력은 테스트 ZIP 검증 범위 밖이며 운영 CDN/API/Publish pipeline은 별도 도입 대상이다. ```text HMDesignSystem/ ├─ packages/ui/src/components/ui/ # Production UI ├─ packages/ui/src/patterns/ # 업무/Layout composition ├─ packages/ui/src/theme/ # CSS Mapping, contract.ts, runtime.ts ├─ packages/center-graphic/src/ # 독립 CENTER 구현 ├─ src/ # Demo, Component/Pattern/Theme/CENTER Story ├─ packages/*/dist/ # compiled ESM JS, 타입 선언, CSS ├─ themes/*.json # 공식 Default / 서비스 partial ├─ themes/drafts/ # 검토용 Theme 자료 ├─ .storybook/ # 통합 검수 환경 ├─ docs/ # Component/Pattern/Theme 문서 └─ package.json # npm workspaces 및 Demo/Storybook build ``` 패키지 산출물에는 공개 컴포넌트·기본 CSS·필요한 계약만 포함하고 Story, Draft Theme, 내부 문서는 제외한다. Theme Publish는 승인된 초안을 검증해 불변 버전으로 저장하고 활성 버전을 API/CDN에서 제공하는 별도 절차다. 패키지 릴리스와 Theme Publish는 같은 저장소의 변경 이력을 참조하지만 버전·승인·롤백을 각각 관리한다. 폴더를 `packages/ui`·`apps/storybook`으로 나눌 필요가 확인되면 **동일 Git 저장소 안에서** 재배치할 수 있다. ### 5.1 패키지 구성과 Storybook 통합 (구현됨 — 2026-10-02 패키지 분리) **하나의 Git 저장소 + 두 개의 독립 배포 패키지 + 하나의 통합 Storybook**을 기본으로 한다. 2026-10-02에 npm workspaces(`packages/*`)로 분리했다. `packages/ui`(`@hm-design/ui`)에 Button·Input·Select·Card·Dialog와 Theme(`src/theme/*.css`, `theme.css`, `tailwind.css`)이 있고, `packages/center-graphic`(`@hm-design/center-graphic`)은 ui-viz CENTER Web Reference(제목·정보·노선/공간·시설물)를 이식해 구현했다(기준 문서 `docs/center-graphic/README.md`). 둘은 서로 의존하지 않는다. 루트는 Storybook(`.storybook/`, `src/**/*.stories.tsx`; UI는 `Components`, CENTER는 `Center Graphic` 최상위 그룹)과 Demo(`src/App.tsx`, `src/hm-theme.css`), 컴포넌트 문서(`docs/components/`)를 유지하며 Story와 Demo는 `@hm-design/ui`를 workspace 패키지로 import한다. 두 패키지는 `private: false`이며 ESM JS·타입 선언·CSS를 compiled `dist`에서 export한다. `npm run build:packages`가 library 산출물을 생성하며 Registry publish 이력은 테스트 ZIP 검증 범위 밖이다. 소비 앱은 `@import "tailwindcss"; @import "@hm-design/ui/tailwind.css"; @import "@hm-design/ui/theme.css"`를 쓴다. UI dist에는 `fonts/`의 글꼴과 OFL이 포함되며 compiled `theme.css`는 상대 `./fonts/` 경로를 사용한다. Source `packages/ui/src/theme.css`는 `/fonts/NotoSansKR-variable.ttf`를 사용하므로 App/Storybook 개발 서버는 `public/fonts/`를 서빙한다. 아래는 목표 구조다. | 패키지 | 내용 | |---|---| | `@hm-design/ui` | shadcn 기반 공통 UI 컴포넌트와 공통 Theme(디자인 Token, CSS Custom Properties, 런타임 Theme 주입) | | `@hm-design/center-graphic` | 서비스 공통 그래픽 리소스와 그래픽 컴포넌트(Icon, Illustration, Empty State·Loading·Status Graphic 등) | ```text HMDesignSystem/ ├─ packages/ │ ├─ ui/ components/ styles/ tokens/ theme/ │ └─ center-graphic/ icons/ illustrations/ graphics/ ├─ storybook/ └─ package.json workspace 설정 ``` - 두 패키지는 따로 설치하고 따로 버전을 올릴 수 있어야 한다. 그래픽만 필요한 프로젝트가 UI 패키지 전체를 설치하지 않도록 한다. - 의존성 방향: UI → Design Token / Theme, center-graphic → 자체 그래픽 리소스. `center-graphic → ui` 필수 의존은 만들지 않고, 공유가 필요하면 최소 수준의 Token만 공유한다. - `tokens` 또는 `theme` 패키지의 별도 분리는 규모가 커지거나 여러 제품이 테마를 독립적으로 공유해야 할 때 검토한다. 그 전에는 두 패키지로 운영한다. - Storybook은 하나로 통합해 UI와 Center Graphic을 함께 검수한다. 메뉴 구조는 패키지 폴더 구조와 같을 필요가 없고 검토 편의를 기준으로 한다. 컴포넌트는 §6.3의 규칙대로 대표 Story가 바로 열리는 1뎁스 항목으로 두고, Playground·Compatibility·verification Story는 기본 메뉴에서 숨긴다. Center Graphic도 같은 원칙을 따른다. - 다른 문서의 `@company/ui`는 `@hm-design/ui`의 자리표시자로 읽고, 이름 치환은 패키징 작업 때 한꺼번에 한다. Storybook 최상위 그룹은 CENTER 구현 시점에 `Center Graphic`만 추가했고 기존 `Components`는 이름을 바꾸지 않았다(`UI Components`로의 개명은 필요할 때 정한다). #### CENTER 규격의 적용 범위 `center-graphic`은 독립 배포 패키지의 경계다. ui_viz의 CENTER 도메인 문서가 정의하는 노선, 시설물, 지도 및 관련 시각화 규격은 `center-graphic`의 구현·검증 작업에서 참조할 수 있다. 반대로 `ui`의 Button, Input, Select 같은 일반 UI 컴포넌트는 CENTER 문서를 디자인 근거로 쓰지 않는다. 따라서 CENTER 문서 참조 제한은 저장소 전체의 금지가 아니라 패키지와 작업 도메인에 따른 적용 범위 규칙이다. - `ui` 작업 → 현재 HM UI 문서·구현·대표 Story를 먼저 참조한다. 외부 ui_viz UI 자료는 제공된 경우에만 참고한다. - `center-graphic` 작업 → CENTER 관련 규격을 참조할 수 있다. - 두 패키지의 규격을 임의로 섞지 않고, 한쪽의 값을 다른 패키지의 근거로 쓰지 않는다. - **CENTER 작업 규칙**: 현재 Source of Truth는 `docs/center-graphic/README.md`, `packages/center-graphic/src/`의 구현·승인 asset과 `src/center/`의 대표 Story다. 외부 ui_viz는 ZIP 미포함 선택적 Reference이며 제공된 경우에만 owner 문서의 명시 상태(`CONFIRMED_CANONICAL`/`CONFIRMED_BY_DESIGN` → `PLATFORM_MAPPING` → `Candidate`/`TBD`/`HOLD`) → 승인 asset → Web Reference 구현 순서로 참고한다. 승인되지 않은 후보 SVG와 미확정값은 구현 근거로 쓰지 않는다. 개별 그래픽 수치는 `docs/center-graphic/`에 기록한다. ## 6. 컴포넌트·Storybook·CSS 작업 규칙 Theme Token의 계층, 매핑 방식, Runtime Theme 적용 방식은 [테마 토큰 가이드](테마-토큰-가이드.md)를 따른다. 이 절에서는 중복해서 정의하지 않는다. React 외 Runtime으로 구현·변환하는 작업은 [Runtime Portability Policy](runtime-portability.md)의 Target Runtime, 동일 Design/Theme Contract, 상대 asset 경로와 실제 실행 검증 기준을 추가로 따른다. 기존 컴포넌트·Token·Runtime Theme을 복제해 별도 Source of Truth를 만들지 않는다. Component / Pattern / Story를 구현하거나 기존 테스트 디자인을 재현할 때는 [shadcn 기반 구현 원칙](shadcn-기반-구현-원칙.md)을 먼저 적용한다. 핵심 우선순위는 **기존 `@hm-design/ui` 재사용 → 기존 Variant / Prop → 기존 Composition → shadcn → Radix Primitive → 최소 Custom 구현**이다. Behavior / Structure는 shadcn/Radix를 유지하고 Visual은 구현 시작부터 HM Theme / Token에 연결한다. Custom 구현은 해당 문서의 허용 조건을 모두 충족하고 사유·범위·Public API 영향을 기록한 경우에만 허용한다. > 패키지 초기화용 base 규칙은 `packages/ui/src/tailwind.css`, Demo 레이아웃 규칙은 `src/hm-theme.css`가 소유한다. Component CSS에는 전역 규칙을 넣지 않는다. 공개 API와 상태·규격은 각 `docs/components/` 문서와 현재 대표 Story를 따른다. Brand label의 Text Border는 두께가 `EXPERIMENTAL`이라 적용하지 않는다. ### 6.1 스타일 값의 출처 1. 현재 HM Component/Pattern 문서와 대표 Story에서 해당 속성의 확정 규격 및 기존 Semantic/Component Token을 확인한다. 2. 기존 Token으로 연결한다. 외부 ui_viz는 제공된 경우에만 HM 정책과 충돌하지 않는 참고 자료로 사용한다. 3. HM 지정값이 없으면 shadcn 기본 구현을 유지한다. 미확정 Reference 값을 확정값으로 쓰지 않는다. HM 지정값이 있는 색상, radius, control height, typography 등은 Theme Token을 사용한다. HM에서 별도 값을 지정하지 않은 hover 동작, focus 구조, 애니메이션, 아이콘 크기, 접근성 속성 등은 shadcn 기본 구현을 유지하며, 임의로 새로 정의하거나 취향에 따라 바꾸지 않는다. ### 6.2 컴포넌트 구현 - 구현 전에 현재 `@hm-design/ui` + shadcn 조합으로 해결 가능한지 확인하고, 가능하면 새 primitive나 유사 Component를 만들지 않는다. - shadcn의 기본 컴포넌트 구조를 최대한 유지한다. CVA `variant`/`size`, `data-slot`, `cn(..., className)`, Radix 구조를 특별한 이유 없이 바꾸지 않는다. - HM 디자인은 기존 구조 안에서 Token과 스타일 값을 연결해 구현한다. HM 스타일만을 위한 wrapper 컴포넌트를 추가하지 않는다. - 기존 export, props 이름, variant 구조 등 공개 API를 불필요하게 바꾸지 않는다. API를 깨는 변경은 별도 합의 후 진행한다. - `