일관성 패턴
12컬럼 그리드, 4px 기반 스페이싱 스케일, mobile-first 반응형 규칙과 layout linting·consistency score로 AI 생성 UI의 일관성을 보장하는 방법.
핵심 요약
- AI 생성 UI의 일관성은 그리드·스페이싱·타입 스케일이라는 명확한 기반 제약이 받쳐줘야 나옵니다.
- 12컬럼 그리드와 Container/Grid 컴포넌트로 화면 구조를 고정하고, 4px 기반 스페이싱 스케일에 component·layout 시맨틱 토큰을 매핑합니다.
- p-[13px], gap-7 같은 임의값·홀수 스케일은 막고 토큰 조합만 허용해 드리프트를 차단합니다.
- 반응형은 mobile-first 원칙 아래 컴포넌트별 규칙(카드 그리드 1→2→3열, 테이블→카드 변환 등)을 명시합니다.
- layout linting 규칙과 가중 합 consistency score(spacing variance·alignment·grid·typography)로 일관성을 수치로 검증하고 CI에 통합합니다.
AI가 생성하는 UI의 일관성은 디자인 시스템의 명확한 제약에 달려 있습니다. 레이아웃 그리드, 스페이싱 시스템, 반응형 전략을 체계로 묶어 두면 AI는 매번 일관된 결과물을 내놓습니다.
일관성 보장 계층
레이아웃 그리드 시스템
그리드 정의
// tokens/grid.ts
export const grid = {
columns: 12,
gutter: {
sm: '16px', // spacing.4
md: '24px', // spacing.6
lg: '32px', // spacing.8
},
margin: {
sm: '16px',
md: '24px',
lg: '32px',
xl: '48px',
},
maxWidth: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
} as constContainer 컴포넌트
// components/layout/container.tsx
interface ContainerProps {
children: React.ReactNode
/** 최대 너비 @default "lg" */
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'
/** 좌우 패딩 포함 @default true */
padded?: boolean
/** 중앙 정렬 @default true */
centered?: boolean
}
/**
* 페이지 콘텐츠 컨테이너
* @ai-hint 페이지 최상위 래퍼로 사용. 대부분의 경우 size="lg" 유지
*/
export function Container({
children,
size = 'lg',
padded = true,
centered = true,
}: ContainerProps) {
return (
<div
className={cn(
'w-full',
centered && 'mx-auto',
padded && 'px-4 md:px-6 lg:px-8',
{
'max-w-screen-sm': size === 'sm',
'max-w-screen-md': size === 'md',
'max-w-screen-lg': size === 'lg',
'max-w-screen-xl': size === 'xl',
'max-w-screen-2xl': size === '2xl',
}
)}
>
{children}
</div>
)
}그리드 레이아웃 패턴
// components/layout/grid.tsx
interface GridProps {
children: React.ReactNode
/** 컬럼 수 @default 12 */
cols?: 1 | 2 | 3 | 4 | 6 | 12
/** 반응형 컬럼 설정 */
responsive?: {
sm?: 1 | 2 | 3 | 4 | 6 | 12
md?: 1 | 2 | 3 | 4 | 6 | 12
lg?: 1 | 2 | 3 | 4 | 6 | 12
}
/** 간격 @default "4" */
gap?: '0' | '2' | '4' | '6' | '8'
}
/**
* @ai-hint 그리드 레이아웃 컨테이너
* - 카드 그리드: cols={3} gap="6"
* - 폼 레이아웃: cols={2} gap="4"
* - 대시보드: responsive={{ sm: 1, md: 2, lg: 4 }}
*/
export function Grid({ children, cols = 12, responsive, gap = '4' }: GridProps) {
// 구현
}스페이싱 시스템
스페이싱 스케일
용도별 스페이싱
// tokens/spacing.ts
export const spacing = {
// Primitive
scale: {
0: '0',
1: '4px',
2: '8px',
3: '12px',
4: '16px',
5: '20px',
6: '24px',
8: '32px',
10: '40px',
12: '48px',
16: '64px',
20: '80px',
},
// Semantic - Component
component: {
/** 인라인 요소 간격 (아이콘-텍스트) */
inline: '{spacing.2}', // 8px
/** 관련 요소 그룹 간격 */
tight: '{spacing.3}', // 12px
/** 기본 내부 패딩 */
padding: '{spacing.4}', // 16px
/** 섹션 내 요소 간격 */
gap: '{spacing.4}', // 16px
/** 카드/모달 내부 패딩 */
inset: '{spacing.6}', // 24px
},
// Semantic - Layout
layout: {
/** 페이지 상하 여백 */
pageY: '{spacing.8}', // 32px
/** 섹션 간 간격 */
section: '{spacing.12}', // 48px
/** 주요 블록 간 간격 */
block: '{spacing.8}', // 32px
},
} as const스페이싱 규칙
## Spacing Rules (CLAUDE.md용)
### Component 내부
- 아이콘-텍스트 간격: `gap-2` (8px)
- 버튼 내부 패딩: `px-4 py-2`
- 카드 내부 패딩: `p-6` (24px)
- 인풋 내부 패딩: `px-3 py-2`
### Component 그룹
- 같은 유형 요소: `gap-2` (8px)
- 폼 필드 간격: `space-y-4` (16px)
- 버튼 그룹: `gap-2` (8px)
### 섹션/레이아웃
- 페이지 상단 여백: `pt-8` (32px)
- 섹션 간 간격: `space-y-12` (48px)
- 카드 그리드 간격: `gap-6` (24px)
### 금지 사항
- 임의의 값 사용 금지: `p-[13px]` ❌
- 홀수 스케일 사용 금지: `gap-7` ❌
- 인라인 스타일 간격 금지반응형 전략
브레이크포인트 시스템
반응형 패턴
// 반응형 그리드 패턴
const responsivePatterns = {
// 카드 그리드: 모바일 1열 → 태블릿 2열 → 데스크톱 3열
cardGrid: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6',
// 사이드바 레이아웃
sidebarLayout: 'flex flex-col lg:flex-row',
sidebar: 'w-full lg:w-64 shrink-0',
main: 'flex-1 min-w-0',
// 폼 레이아웃: 모바일 스택 → 데스크톱 2열
formGrid: 'grid grid-cols-1 md:grid-cols-2 gap-4',
// 히어로 섹션
hero: 'py-12 md:py-20 lg:py-32',
heroTitle: 'text-3xl md:text-4xl lg:text-5xl',
// 숨김/표시
mobileOnly: 'block lg:hidden',
desktopOnly: 'hidden lg:block',
}컴포넌트별 반응형 규칙
## Responsive Rules (CLAUDE.md용)
### Navigation
- 모바일: 햄버거 메뉴 + 드로어
- 데스크톱 (lg+): 수평 메뉴
### Card Grid
- < sm: 1열
- sm ~ lg: 2열
- lg+: 3열 또는 4열
### Form
- 모바일: 전체 너비, 스택 레이아웃
- md+: 2열 그리드 가능
### Dialog/Modal
- 모바일: 전체 화면 (드로어 스타일)
- md+: 중앙 모달
### Table
- 모바일: 카드 리스트로 변환 또는 수평 스크롤
- md+: 표준 테이블
### Typography
- 모바일: 기본 크기
- lg+: 히어로/제목은 스케일업일관성 검증
Layout Linting
// scripts/lint-layout.ts
interface LayoutRule {
name: string
test: (node: TSNode) => boolean
message: string
}
const layoutRules: LayoutRule[] = [
{
name: 'no-arbitrary-spacing',
test: (node) => /\b(p|m|gap|space)-\[\d+px\]/.test(node.text),
message: '임의의 간격 값 대신 토큰을 사용하세요.',
},
{
name: 'container-required',
test: (node) => isPageComponent(node) && !hasContainer(node),
message: '페이지 컴포넌트는 Container로 감싸야 합니다.',
},
{
name: 'consistent-card-padding',
test: (node) => isCard(node) && !hasStandardPadding(node),
message: 'Card 내부 패딩은 p-6을 사용하세요.',
},
]Visual Consistency Score
// scripts/consistency-score.ts
interface ConsistencyMetrics {
spacingVariance: number // 0-100: 낮을수록 좋음
alignmentScore: number // 0-100: 높을수록 좋음
gridCompliance: number // 0-100: 그리드 준수율
typographyScore: number // 0-100: 타입 스케일 준수율
}
function calculateConsistencyScore(metrics: ConsistencyMetrics): number {
const weights = {
spacingVariance: 0.3,
alignmentScore: 0.25,
gridCompliance: 0.25,
typographyScore: 0.2,
}
return (
(100 - metrics.spacingVariance) * weights.spacingVariance +
metrics.alignmentScore * weights.alignmentScore +
metrics.gridCompliance * weights.gridCompliance +
metrics.typographyScore * weights.typographyScore
)
}