[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -2,93 +2,215 @@
|
||||
id: wiki-2026-0508-component-library-architecture
|
||||
title: Component Library Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: []
|
||||
aliases: [Design System Architecture, UI Library Design]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [uncategorized]
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [components, design-system, react, shadcn, radix]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: typescript
|
||||
framework: react-radix-shadcn
|
||||
---
|
||||
|
||||
# [[Component Library Architecture|Component Library Architecture]]
|
||||
# Component Library Architecture
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
컴포넌트 라이브러리 아키텍처는 확장 가능하고 유지보수가 용이한 프론트엔드 애플리케이션을 구축하기 위해 재사용 가능한 UI 컴포넌트를 설계하고 조직화하는 구조적 접근 방식입니다 [1, 2]. 이는 단순한 시각적 요소를 넘어, 컴포넌트 간의 상태 공유, 로직 분리, 아키텍처 패턴을 활용하여 일관성 있는 시스템을 구현하는 것을 목표로 합니다 [3-5]. 잘 설계된 아키텍처는 과도한 상태 전달([[Prop Drilling|Prop Drilling]])을 방지하고 높은 유연성을 제공하여 끊임없이 변화하는 제품 요구사항에 안전하게 대처할 수 있게 합니다 [1, 6-8].
|
||||
## 매 한 줄
|
||||
> **"매 unstyled headless primitives + 매 style layer + 매 composition API."**. 2026년 React component library 의 dominant model = Radix/Ark UI (headless behavior + a11y) + Tailwind/CSS variables (theming) + shadcn/ui (copy-not-install) + tokens (DTCG). MUI 5 의 monolithic theme 시대 → headless 시대로 매 shift.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
* **아토믹 디자인([[Atomic Design|Atomic Design]]) 방법론:** UI를 원자(Atoms), 분자(Molecules), 유기체(Organisms), 템플릿(Templates), 페이지(Pages) 등 5단계의 계층으로 나누어 구조화하는 멘탈 모델입니다 [2, 9, 10]. 이는 디자인 시스템의 일관성을 촉진하지만, 복잡한 비즈니스 로직과 결합될 때는 UI 라이브러리에만 아토믹 구조를 적용하고 애플리케이션 코드는 기능(Feature) 기반으로 분리하여 비즈니스 로직을 캡슐화하는 하이브리드 방식이 주로 권장됩니다 [11, 12].
|
||||
* **복합 컴포넌트([[Compound Components|Compound Components]]) 패턴:** 탭(Tabs)이나 아코디언(Accordion)과 같이 밀접하게 연관된 하위 컴포넌트들이 [[React Context|React Context]]를 통해 암시적으로 상태를 공유하는 패턴입니다 [13-15]. 무수히 많은 Prop을 하위로 계속 넘겨주는 대신, 컴포넌트 소비자가 직접 하위 요소를 조합할 수 있게 하여 레이아웃의 유연성을 극대화하고 API를 깔끔하게 유지합니다 [3, 6, 16, 17].
|
||||
* **헤드리스 컴포넌트([[Headless Components|Headless Components]]):** 접근성, 상태 관리, 비즈니스 로직 등 핵심 기능만 제공하고 시각적인 마크업과 스타일링은 전적으로 개발자에게 위임하는 방식입니다 [18, 19]. 주로 [[Tailwind CSS|Tailwind CSS]]와 결합하여 특정 프레임워크나 디자인 시스템에 종속되지 않는 고도로 맞춤화된 UI 라이브러리를 구축하는 데 사용됩니다 [18, 20].
|
||||
* **오버라이드 패턴([[Overrides Pattern|Overrides Pattern]]):** Uber의 Base Web 시스템 등에서 활용하는 아키텍처로, 컴포넌트 내부의 모든 하위 요소에 접근할 수 있는 식별자를 제공하여 스타일, 속성, 혹은 렌더링되는 컴포넌트 자체를 통째로 교체할 수 있게 합니다 [21-24]. 이를 통해 모든 엣지 케이스마다 새로운 Prop을 추가하여 발생하는 복잡성(Prop soup)을 방지하고 심층적인 커스터마이징을 지원합니다 [23, 24].
|
||||
* **기능 분할 설계([[Feature-Sliced Design|Feature-Sliced Design]], FSD) 및 모노레포:** 거대한 컴포넌트 라이브러리와 다수의 애플리케이션이 공존할 때, 단방향 의존성 흐름을 강제하는 구조입니다 [25-28]. Shared(공유 UI 기본 요소, 디자인 토큰 등), Entities, Features, Widgets, Pages 등의 계층으로 명확히 나누어 모노레포([[Turborepo|Turborepo]], Nx 등) 내에서 안정적이고 격리된 아키텍처를 유지할 수 있게 합니다 [27, 29-31].
|
||||
* **디자인 토큰([[Design Tokens|Design Tokens]]) 계층화:** 색상, 타이포그래피, 간격 등의 원시 값을 추상화하여 기본 토큰(Primitives), 시맨틱 토큰(Semantic), 컴포넌트 토큰(Component Tokens)의 3단계 계층 구조로 관리합니다 [32-35]. 이는 테마(예: 다크 모드)의 동적 전환을 용이하게 하고 라이브러리 전반의 시각적 일관성과 안전한 리팩토링을 보장합니다 [5, 36-38].
|
||||
## 매 핵심
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- **Related Topics:** [[Atomic Design|Atomic Design]], Compound Components, Headless Components, [[Design Tokens|Design Tokens]], [[Feature-Sliced Design|Feature-Sliced Design]]
|
||||
- **Projects/Contexts:** [[Uber Base Web|Uber Base Web]], Shopify Polaris, React Server Components (RSC, Tailwind CSS vs [[Styled Components|Styled Components]]
|
||||
- **Contradictions/Notes:** 복합 컴포넌트 패턴은 높은 유연성을 주지만 과용하면 소비자에게 너무 많은 통제권을 주어 UX나 접근성 등 구조적 일관성이 깨질 위험이 있습니다. 따라서 레이아웃이 고정되어 있는 단순한 버튼이나 배지 같은 컴포넌트에는 일반적인 Prop 기반 방식이 훨씬 적합합니다 [39-41]. 또한, 컴포넌트 스타일링 구현 시 Styled Components처럼 런타임에 스타일을 주입하는 방식은 동적 스타일링에 강력하나 [[Next.js 15|Next.js 15]]의 App Router 및 RSC 환경에서는 Context 부재로 인한 구조적 제약과 번들 사이즈 등 성능 비용이 따릅니다 [42-45]. 이 때문에 최신 프론트엔드 아키텍처는 정적 CSS 생성이 가능한 Tailwind CSS 또는 Zero-runtime 방식([[vanilla-extract|vanilla-extract]] 등)을 컴포넌트 라이브러리 구축에 더 권장하는 추세입니다 [46-49].
|
||||
### 매 3 layer architecture
|
||||
1. **Behavior layer (headless)**: a11y, keyboard, focus, ARIA — Radix, Ark UI, React Aria.
|
||||
2. **Style layer**: Tailwind, CSS-in-JS (vanilla-extract), CSS variables.
|
||||
3. **Composition layer**: app-specific compositions of 1+2.
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-26*
|
||||
### 매 distribution model
|
||||
- **Package install** (MUI, Chakra v3): npm install, version locked, hard to customize.
|
||||
- **Copy-paste** (shadcn/ui): generator copies source into your repo, you own it, fully customizable.
|
||||
- **Hybrid** (Radix + custom wrapper): primitives via npm, your styles in repo.
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
### 매 token system (DTCG / Style Dictionary)
|
||||
- Primitive tokens (color.blue.500 = #3b82f6).
|
||||
- Semantic tokens (color.action.primary = color.blue.500).
|
||||
- Component tokens (button.bg.primary = color.action.primary).
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
### 매 응용
|
||||
1. Internal design system (Vercel Geist, GitHub Primer).
|
||||
2. Open-source kits (shadcn/ui, Mantine, Park UI).
|
||||
3. White-label products (multi-brand 동일 codebase).
|
||||
4. Cross-framework via Web Components (Spectrum Web Components).
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
## 💻 패턴
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
### Headless primitive (Radix)
|
||||
```typescript
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
|
||||
- **과거 데이터와의 충돌:** 없음
|
||||
- **정책 변화:** 없음
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
export function ConfirmDialog({ open, onClose, onConfirm, children }: Props) {
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
|
||||
<Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6">
|
||||
<Dialog.Title className="text-lg font-semibold">Confirm</Dialog.Title>
|
||||
<Dialog.Description>{children}</Dialog.Description>
|
||||
<div className="mt-4 flex justify-end gap-2">
|
||||
<Dialog.Close className="btn-ghost">Cancel</Dialog.Close>
|
||||
<button className="btn-primary" onClick={onConfirm}>OK</button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
### shadcn-style copy-paste component
|
||||
```typescript
|
||||
// components/ui/button.tsx — owned by your repo
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
ghost: 'hover:bg-gray-100',
|
||||
},
|
||||
size: {
|
||||
sm: 'h-8 px-3',
|
||||
md: 'h-10 px-4',
|
||||
lg: 'h-12 px-6',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'primary', size: 'md' },
|
||||
},
|
||||
);
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
type Props = React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
& VariantProps<typeof buttonVariants>;
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
export function Button({ className, variant, size, ...rest }: Props) {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### Design tokens (DTCG JSON)
|
||||
```json
|
||||
{
|
||||
"color": {
|
||||
"blue": {
|
||||
"500": { "$value": "#3b82f6", "$type": "color" }
|
||||
},
|
||||
"action": {
|
||||
"primary": { "$value": "{color.blue.500}", "$type": "color" }
|
||||
}
|
||||
},
|
||||
"space": {
|
||||
"4": { "$value": "16px", "$type": "dimension" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
### CSS variables driven theming
|
||||
```css
|
||||
:root {
|
||||
--color-primary: #3b82f6;
|
||||
--radius-md: 0.5rem;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--color-primary: #60a5fa;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
```
|
||||
|
||||
### Polymorphic component (asChild pattern)
|
||||
```typescript
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
|
||||
type Props = {
|
||||
asChild?: boolean;
|
||||
} & React.HTMLAttributes<HTMLElement>;
|
||||
|
||||
export function Card({ asChild, ...rest }: Props) {
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
return <Comp className="rounded-lg border p-4" {...rest} />;
|
||||
}
|
||||
|
||||
// Usage:
|
||||
<Card asChild>
|
||||
<a href="/post/1">Click me</a>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Storybook 8 + a11y addon
|
||||
```typescript
|
||||
// Button.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from './button';
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
component: Button,
|
||||
parameters: { a11y: { test: 'error' } },
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const Primary: StoryObj<typeof Button> = {
|
||||
args: { children: 'Save', variant: 'primary' },
|
||||
};
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Internal design system, full control | shadcn/ui copy-paste + Tailwind |
|
||||
| Quick prototype, full kit | Mantine, Chakra v3 |
|
||||
| Cross-framework | Lit + Web Components |
|
||||
| Strong a11y 요구 | Radix or React Aria |
|
||||
| Multi-brand white-label | Token-driven CSS variables |
|
||||
|
||||
**기본값**: React 19 + Radix primitives + Tailwind v4 + shadcn/ui structure + DTCG tokens.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Component-Based Architecture (CBA)]] · [[Design Systems]]
|
||||
- 변형: [[Compound Components]] · [[Headless UI]]
|
||||
- 응용: [[shadcn-ui]] · [[Radix UI]] · [[Storybook]]
|
||||
- Adjacent: [[Tailwind CSS]] · [[Design Tokens]] · [[A11y]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: starting design system, refactoring 중복 UI, multi-product/brand consolidation.
|
||||
**언제 X**: 1-page landing site (overhead 큼), heavily 3D/canvas UI (HTML primitive 안 맞음).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Tightly styled primitives**: behavior + style 의 강결합 — replace 시 rewrite 필요.
|
||||
- **Theme prop drilling**: 매 component 가 theme prop 받음 — CSS variables 로.
|
||||
- **No tokens**: hex color 직접 사용 — token system 부재 시 multi-brand 불가.
|
||||
- **Locked-in vendor library**: MUI v3→v5 같은 대규모 breaking change — headless + own styles 가 안전.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (shadcn/ui docs 2026 / Radix UI / DTCG W3C draft / React 19 docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 3-layer model + shadcn/Radix/DTCG 패턴 |
|
||||
|
||||
Reference in New Issue
Block a user