생성형 UI
AI가 런타임에 UI를 동적 생성하는 4단계 스펙트럼, CopilotKit·Vercel AI SDK·A2UI 프레임워크, 컴포넌트 레지스트리 허용 목록과 보안·접근성 게이트.
핵심 요약
- 생성형 UI는 LLM이 텍스트 대신 React 컴포넌트를 스트리밍하는 패턴입니다. Gartner는 2026년까지 신규 앱의 30%가 AI 기반 적응형 인터페이스를 쓸 것으로 내다봤습니다.
- AI 개입 수준은 템플릿(L1)·파라미터(L2)·레이아웃(L3)·완전 생성(L4)의 4단계이며, 프로덕션은 L1~2를 권장합니다.
- CopilotKit(useCopilotAction+render), Vercel AI SDK(streamUI), Google A2UI(선언적 JSON)가 대표 프레임워크입니다.
- 디자인 시스템은 컴포넌트 레지스트리 허용 목록으로 AI가 검증된 컴포넌트만 고르게 묶어두고, props는 Zod 스키마로 검증합니다.
- AI 생성 코드의 약 45%에 보안 취약점이 있어 허용 목록·XSS 이스케이프·axe-core 접근성 게이트가 필수이며 L4는 프로덕션 사용을 금지합니다.
기존 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" }
]
}