생성형 UI
AI가 런타임에 사용자 인터페이스를 동적으로 생성하는 패러다임
기존 UI는 개발자가 빌드 타임에 모든 화면을 미리 정의합니다. 생성형 UI는 AI가 런타임에 사용자 컨텍스트에 맞춰 인터페이스를 동적으로 구성하는 패러다임입니다. 디자인 시스템은 이 과정에서 품질과 일관성을 보장하는 핵심 인프라 역할을 합니다.
정적 UI vs 생성형 UI
Gartner는 2026년까지 신규 앱의 30%가 AI 기반 적응형 인터페이스를 사용할 것으로 예측했습니다. 2년 전만 해도 이 비율은 5% 미만이었습니다. AI 에이전트가 대화 맥락을 이해하고 적절한 UI 컴포넌트를 실시간으로 선택·조합하는 시대가 열리고 있습니다.
생성형 UI란?
LLM이 텍스트 응답 대신 React 컴포넌트를 스트리밍하여 사용자에게 인터랙티브한 인터페이스를 제공하는 패턴입니다. 채팅 인터페이스에서 날씨 카드, 주식 차트, 예약 폼 등을 AI가 맥락에 맞게 선택하여 렌더링합니다.
생성형 UI의 스펙트럼
생성형 UI는 AI 개입 수준에 따라 4단계로 구분할 수 있습니다.
| 단계 | 방식 | AI 역할 | 안전성 | 사용 사례 |
|---|---|---|---|---|
| Level 1 | 템플릿 기반 | 미리 정의된 컴포넌트 선택 | 매우 높음 | 챗봇 응답 카드 |
| Level 2 | 파라미터 기반 | 컴포넌트 props/데이터 동적 결정 | 높음 | 대시보드 위젯 |
| Level 3 | 레이아웃 생성 | 컴포넌트 배치와 구조 결정 | 보통 | 적응형 페이지 |
| Level 4 | 완전 생성 | 새로운 컴포넌트를 런타임에 생성 | 낮음 | 프로토타이핑 |
안전성과 자유도의 트레이드오프
Level이 높아질수록 AI의 자유도가 커지지만 예측 가능성은 낮아집니다. 프로덕션 환경에서는 Level 1~2를 권장하며, Level 3 이상은 디자인 시스템의 엄격한 제약 하에서만 사용하세요.
주요 프레임워크 비교
2026년 현재 생성형 UI를 구현할 수 있는 대표적인 프레임워크를 비교합니다.
| 프레임워크 | 특징 | 적합한 경우 |
|---|---|---|
| CopilotKit | React 최적, AG-UI 프로토콜, 스트리밍 + 도구 호출 + HITL + 생성형 UI | React 프로젝트, 에이전트 기능 필요 |
| assistant-ui | Y Combinator W25, 월 5만+ 다운로드, AI 채팅 UI 라이브러리 | 채팅 중심 인터페이스 |
| Google A2UI | 선언적 JSON, 프레임워크 무관, 크로스플랫폼 | 멀티플랫폼 에이전트 |
| Vercel AI SDK | streamUI(), Next.js 최적화, RSC 기반 | Next.js 프로젝트 |
CopilotKit vs Vercel AI SDK
// CopilotKit — useCopilotAction + render 패턴
useCopilotAction({
name: 'showWeather',
description: '날씨 정보를 카드로 표시',
parameters: [
{ name: 'city', type: 'string', description: '도시 이름' },
],
render: ({ args, status }) => {
if (status === 'executing') return <WeatherSkeleton />
return <WeatherCard city={args.city} />
},
handler: async ({ city }) => {
return await fetchWeather(city)
},
})
// Vercel AI SDK — streamUI 패턴
const result = await streamUI({
model: openai('gpt-4o'),
messages,
tools: {
showWeather: {
description: '날씨 정보를 카드로 표시',
parameters: z.object({ city: z.string() }),
generate: async function* ({ city }) {
yield <WeatherSkeleton />
const data = await fetchWeather(city)
return <WeatherCard data={data} />
},
},
},
})디자인 시스템과 생성형 UI의 통합
생성형 UI에서 디자인 시스템은 AI가 사용할 수 있는 컴포넌트의 경계를 정의합니다. AI가 아무 HTML이나 생성하는 것이 아니라, 검증된 컴포넌트 카탈로그 안에서 선택하도록 제약합니다.
컴포넌트 레지스트리
AI가 접근할 수 있는 컴포넌트를 허용 목록(whitelist) 방식으로 관리합니다.
// component-registry.ts
import { z } from 'zod'
interface RegistryEntry<T extends z.ZodType> {
component: React.ComponentType<z.infer<T>>
props: T
description: string
tags: string[]
}
const registry = {
'weather-card': {
component: WeatherCard,
props: z.object({
city: z.string(),
temperature: z.number(),
condition: z.enum(['sunny', 'cloudy', 'rainy', 'snowy']),
}),
description: '날씨 정보를 카드 형태로 표시',
tags: ['data-display', 'card'],
},
'stock-chart': {
component: StockChart,
props: z.object({
symbol: z.string(),
period: z.enum(['1d', '1w', '1m', '3m', '1y']),
}),
description: '주식 시세를 차트로 시각화',
tags: ['data-display', 'chart'],
},
'booking-form': {
component: BookingForm,
props: z.object({
serviceType: z.enum(['hotel', 'flight', 'restaurant']),
defaultDate: z.string().optional(),
}),
description: '예약 양식을 렌더링',
tags: ['form', 'interactive'],
},
} satisfies Record<string, RegistryEntry<z.ZodType>>디자인 토큰 기반 품질 보장
레지스트리에 등록된 컴포넌트는 디자인 토큰을 준수하도록 사전 검증되어 있어야 합니다.
// validate-registry.ts
function validateRegistryComponent(name: string) {
const entry = registry[name]
const testElement = render(<entry.component {...mockProps(entry.props)} />)
return {
// 디자인 토큰 사용 여부
usesDesignTokens: !containsHardcodedValues(testElement),
// 접근성 준수 여부
a11yCompliant: await runAxeCheck(testElement),
// 반응형 대응 여부
responsive: checkResponsiveBreakpoints(testElement),
}
}CopilotKit 실전 예제
React 프로젝트에서 CopilotKit의 useCopilotAction + render 패턴으로 생성형 UI를 구현합니다.
// components/copilot-actions.tsx
'use client'
import { useCopilotAction } from '@copilotkit/react-core'
export function useDashboardActions() {
useCopilotAction({
name: 'renderChart',
description: '데이터를 차트로 시각화합니다',
parameters: [
{ name: 'type', type: 'string', description: 'bar | line | pie' },
{ name: 'title', type: 'string', description: '차트 제목' },
{ name: 'data', type: 'object[]', description: '차트 데이터 배열' },
],
render: ({ args, status }) => {
// status: 'executing' → 'inProgress' → 'complete'
if (status === 'executing') return <ChartSkeleton />
return <ChartCard type={args.type} title={args.title} data={args.data} />
},
handler: async ({ type, title, data }) => {
return { success: true, rendered: true }
},
})
}CopilotKit은 AI 응답 생성 중 점진적으로 UI를 렌더링합니다.
status 파라미터(executing → inProgress → complete)로 로딩 전환을 처리합니다.
품질 보장 전략
디자인 토큰 준수 자동 검증
생성된 UI가 디자인 시스템의 토큰을 올바르게 사용하는지 자동으로 검증합니다.
// lint-generated-ui.ts
function lintGeneratedUI(element: React.ReactElement): LintResult {
const violations: Violation[] = []
// 하드코딩된 색상 값 감지
if (containsHardcodedColors(element)) {
violations.push({ rule: 'no-hardcoded-colors', severity: 'error' })
}
// 비표준 간격 값 감지
if (containsNonStandardSpacing(element)) {
violations.push({ rule: 'standard-spacing', severity: 'warning' })
}
return { passed: violations.length === 0, violations }
}접근성 자동 검증
axe-core를 활용하여 생성된 UI의 접근성을 런타임에 검증합니다. 위반 사항이 발견되면 프로덕션에서는 폴백 UI로 전환합니다.
// a11y-gate.ts
import { axe } from 'axe-core'
async function validateA11y(container: HTMLElement) {
const results = await axe.run(container)
if (results.violations.length > 0) {
return { valid: false, fallback: <DefaultFallbackUI /> }
}
return { valid: true }
}시각적 회귀 테스트
생성형 UI는 출력이 비결정적이므로, Percy나 Chromatic 같은 도구로 구조적 유사도 기반 테스트를 적용합니다. 동일 프롬프트에 대해 동일 컴포넌트 타입이 선택되는지, DOM 트리 유사도가 90% 이상인지를 검증합니다.
보안 고려사항
보안 경고: AI 생성 코드의 취약점
Veracode 연구에 따르면 AI가 생성한 코드의 약 45%에 보안 취약점이 포함되어 있습니다. 생성형 UI에서는 반드시 허용 목록(whitelist) 방식을 사용하고, AI가 임의의 HTML/JSX를 직접 생성하는 Level 4 방식은 프로덕션에서 사용하지 마세요.
보안을 위한 필수 가드레일:
// security-guardrails.ts
const ALLOWED_COMPONENTS = new Set(Object.keys(registry))
function sanitizeGeneratedUI(spec: GeneratedUISpec): SafeUISpec {
// 1. 허용된 컴포넌트만 통과
if (!ALLOWED_COMPONENTS.has(spec.component)) {
throw new SecurityError(`허용되지 않은 컴포넌트: ${spec.component}`)
}
// 2. Props를 Zod 스키마로 검증
const entry = registry[spec.component]
const validatedProps = entry.props.parse(spec.props)
// 3. XSS 방지: 문자열 props 이스케이프
const sanitizedProps = sanitizeStringProps(validatedProps)
return { component: spec.component, props: sanitizedProps }
}Google A2UI: 선언적 접근
Google의 Agent-to-UI(A2UI)는 프레임워크에 종속되지 않는 선언적 JSON 방식입니다. AI가 JSON UI 명세를 출력하면, 각 플랫폼(React, Flutter, SwiftUI)의 렌더러가 네이티브 컴포넌트로 변환합니다.
{
"type": "card",
"title": "서울 날씨",
"content": [
{ "type": "metric", "label": "현재 기온", "value": "24°C" },
{
"type": "chart",
"chartType": "line",
"data": { "labels": ["06시", "12시", "18시"], "values": [18, 24, 20] }
}
],
"actions": [
{ "label": "상세 보기", "action": "navigate", "target": "/weather/seoul" }
]
}