본문으로 바로가기
리옵트 핸드북
리옵트 핸드북
AI 시대의 디자인 시스템

기반 설계

토큰 아키텍처컴포넌트 명세접근성 내장

AI 워크플로우

프롬프트 인터페이스DESIGN.md 인터페이스에이전틱 디자인 품질 제어워크플로우 전략AI 기반 DS 진화컨텍스트 주입

사용자 경험

일관성 패턴인터랙션 설계폼 & 데이터 입력

차세대 인터페이스

에이전트 UI 프로토콜생성형 UI공간·멀티모달 인터페이스

실전

실행 플레이북마이그레이션 전략거버넌스 & 협업사례 연구

운영

검증 체크리스트업데이트 로그
핸드북›AI 시대의 디자인 시스템›접근성 내장
한국어English

접근성 내장

AI가 생성하는 컴포넌트에 ARIA, 키보드 조작, 포커스 관리, 검증 규칙을 기본값으로 내장하는 디자인 시스템 전략입니다.

핵심 요약

  • 접근성을 Primitive → Component → Pattern → Page 계층으로 디자인 시스템에 박아 두면, AI가 만든 컴포넌트가 처음부터 접근성을 갖춥니다.
  • 컴포넌트별 role 매핑, aria-* 상태·관계 속성, 표준 키보드 패턴(Tab·Enter/Space·Escape·방향키), 포커스 트랩을 코드로 표준화합니다.
  • 색상 대비는 일반 텍스트 AA 4.5:1, 대형 텍스트·UI 컴포넌트 3:1, AAA 7:1 기준을 토큰 페어링과 자동 검사 스크립트로 보장합니다.
  • axe-core 유닛 테스트와 Playwright AxeBuilder로 CI에서 WCAG 2.1 AA 위반을 차단합니다.
  • AI 접근성 도구는 웹 이슈 약 60%, 모바일 30% 미만만 감지하므로 인지 접근성·보조 기술 호환성은 여전히 수동 테스트가 필수입니다.

접근성은 선택이 아닌 기본값입니다. AI가 만드는 모든 컴포넌트가 접근성을 갖추려면 디자인 시스템 수준에서 ARIA 패턴을 미리 박아 둬야 합니다.

접근성 내장 전략

ARIA 패턴 표준화

역할(Role) 매핑

// accessibility/roles.ts

/**
 * 컴포넌트-ARIA Role 매핑
 * @ai-hint 컴포넌트 생성 시 자동으로 적절한 role 적용
 */
export const componentRoleMap = {
  // 인터랙티브
  Button: 'button',
  Link: 'link',
  Checkbox: 'checkbox',
  Radio: 'radio',
  Switch: 'switch',
  Slider: 'slider',

  // 컨테이너
  Dialog: 'dialog',
  AlertDialog: 'alertdialog',
  Menu: 'menu',
  Listbox: 'listbox',
  Combobox: 'combobox',
  TabList: 'tablist',
  Tab: 'tab',
  TabPanel: 'tabpanel',

  // 정보 표시
  Alert: 'alert',
  Status: 'status',
  Tooltip: 'tooltip',
  ProgressBar: 'progressbar',

  // 랜드마크
  Navigation: 'navigation',
  Main: 'main',
  Complementary: 'complementary',
  Region: 'region',
} as const

상태(State) 속성 패턴

// accessibility/states.ts

interface AriaState {
  /** 확장/축소 상태 */
  'aria-expanded'?: boolean
  /** 선택 상태 (단일 선택) */
  'aria-selected'?: boolean
  /** 체크 상태 (true/false/mixed) */
  'aria-checked'?: boolean | 'mixed'
  /** 비활성화 */
  'aria-disabled'?: boolean
  /** 눌림 상태 (토글 버튼) */
  'aria-pressed'?: boolean | 'mixed'
  /** 숨김 (스크린리더에서) */
  'aria-hidden'?: boolean
  /** 현재 항목 */
  'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | true
  /** 유효하지 않은 입력 */
  'aria-invalid'?: boolean | 'grammar' | 'spelling'
  /** 필수 입력 */
  'aria-required'?: boolean
  /** 로딩/진행 중 */
  'aria-busy'?: boolean
}

관계(Relationship) 속성

// accessibility/relationships.ts

interface AriaRelationship {
  /** 설명하는 요소 ID */
  'aria-describedby'?: string
  /** 레이블하는 요소 ID */
  'aria-labelledby'?: string
  /** 제어하는 요소 ID */
  'aria-controls'?: string
  /** 소유하는 요소 ID */
  'aria-owns'?: string
  /** 활성 자손 요소 ID */
  'aria-activedescendant'?: string
  /** 에러 메시지 요소 ID */
  'aria-errormessage'?: string
}

/**
 * 관계 ID 생성 유틸리티
 * @ai-hint 컴포넌트 간 ARIA 관계 설정 시 사용
 */
export function createAriaIds(baseId: string) {
  return {
    root: baseId,
    label: `${baseId}-label`,
    description: `${baseId}-desc`,
    error: `${baseId}-error`,
    listbox: `${baseId}-listbox`,
    option: (index: number) => `${baseId}-option-${index}`,
  }
}

키보드 인터랙션 패턴

표준 키보드 패턴

// accessibility/keyboard.ts

/**
 * 컴포넌트별 키보드 인터랙션 패턴
 * @ai-hint 컴포넌트 구현 시 해당 키 핸들링 필수
 */
export const keyboardPatterns = {
  Button: {
    Enter: 'activate',
    Space: 'activate',
  },

  Menu: {
    Enter: 'select',
    Space: 'select',
    ArrowDown: 'next',
    ArrowUp: 'previous',
    Home: 'first',
    End: 'last',
    Escape: 'close',
    Tab: 'close and move focus',
  },

  Tabs: {
    ArrowLeft: 'previous tab',
    ArrowRight: 'next tab',
    Home: 'first tab',
    End: 'last tab',
    Enter: 'activate (manual)',
    Space: 'activate (manual)',
  },

  Combobox: {
    ArrowDown: 'open or next',
    ArrowUp: 'previous',
    Enter: 'select',
    Escape: 'close',
    Home: 'first (open)',
    End: 'last (open)',
    // 문자 입력: typeahead
  },

  Dialog: {
    Tab: 'cycle focus within',
    Escape: 'close',
  },

  Slider: {
    ArrowRight: 'increase',
    ArrowUp: 'increase',
    ArrowLeft: 'decrease',
    ArrowDown: 'decrease',
    Home: 'minimum',
    End: 'maximum',
    PageUp: 'large increase',
    PageDown: 'large decrease',
  },
} as const

포커스 트랩 구현

// components/focus-trap/index.tsx

interface FocusTrapProps {
  children: React.ReactNode
  /** 트랩 활성화 여부 */
  active?: boolean
  /** 최초 포커스 요소 선택자 */
  initialFocus?: string
  /** 비활성화 시 복원할 포커스 요소 */
  returnFocus?: boolean
}

/**
 * 포커스 트랩 컴포넌트
 * @ai-hint Dialog, Modal, Drawer 내부에서 사용
 * - Tab 순환 (마지막 → 첫 번째)
 * - Shift+Tab 역순환
 * - 외부 요소 포커스 방지
 */
export function FocusTrap({
  children,
  active = true,
  initialFocus,
  returnFocus = true,
}: FocusTrapProps) {
  // 구현 생략
}

스크린리더 전용 텍스트

VisuallyHidden 컴포넌트

// components/visually-hidden/index.tsx

/**
 * 시각적으로 숨기되 스크린리더에는 노출
 * @ai-hint 아이콘 버튼, 복잡한 시각 요소에 레이블 추가 시 사용
 */
export function VisuallyHidden({ children }: { children: React.ReactNode }) {
  return (
    <span
      style={{
        position: 'absolute',
        width: '1px',
        height: '1px',
        padding: 0,
        margin: '-1px',
        overflow: 'hidden',
        clip: 'rect(0, 0, 0, 0)',
        whiteSpace: 'nowrap',
        border: 0,
      }}
    >
      {children}
    </span>
  )
}

// 사용 예시
<Button>
  <SearchIcon aria-hidden="true" />
  <VisuallyHidden>검색</VisuallyHidden>
</Button>

Live Region 패턴

// components/announcer/index.tsx

interface AnnouncerProps {
  /** 알림 메시지 */
  message: string
  /** 긴급도: polite(대기) | assertive(즉시) */
  politeness?: 'polite' | 'assertive'
}

/**
 * 스크린리더 알림 컴포넌트
 * @ai-hint 동적 콘텐츠 변경, 폼 제출 결과 등에서 사용
 */
export function Announcer({ message, politeness = 'polite' }: AnnouncerProps) {
  return (
    <div
      role="status"
      aria-live={politeness}
      aria-atomic="true"
      className="sr-only"
    >
      {message}
    </div>
  )
}

색상 대비 검증

대비 비율 기준

수준일반 텍스트대형 텍스트UI 컴포넌트
AA4.5:13:13:1
AAA7:14.5:1-

대형 텍스트 기준

18px 이상 또는 14px Bold 이상

토큰 기반 대비 보장

// tokens/colors.contrast.ts

/**
 * 전경-배경 페어링 정의
 * @ai-hint 이 페어링만 사용하면 WCAG AA 보장
 */
export const contrastPairs = {
  // 기본 텍스트
  'foreground.default': {
    on: ['background.default', 'background.surface'],
    contrast: 7.2, // AAA
  },
  'foreground.muted': {
    on: ['background.default', 'background.surface'],
    contrast: 4.5, // AA
  },

  // 인터랙티브
  primary: {
    on: ['background.default'],
    contrast: 4.5,
    foreground: 'foreground.inverted', // 위에 올라갈 텍스트
    foregroundContrast: 4.8,
  },
  destructive: {
    on: ['background.default'],
    contrast: 4.5,
    foreground: 'foreground.inverted',
    foregroundContrast: 5.1,
  },

  // 보더
  'border.default': {
    on: ['background.default'],
    contrast: 3.0, // UI 컴포넌트 기준
  },
} as const

자동 대비 검사 스크립트

// scripts/check-contrast.ts

import { getContrastRatio } from './utils'
import { colors, contrastPairs } from '../tokens'

function checkAllContrasts() {
  const failures: string[] = []

  for (const [fg, config] of Object.entries(contrastPairs)) {
    for (const bg of config.on) {
      const ratio = getContrastRatio(colors[fg], colors[bg])
      if (ratio < config.contrast) {
        failures.push(`${fg} on ${bg}: ${ratio.toFixed(2)} < ${config.contrast}`)
      }
    }
  }

  if (failures.length > 0) {
    console.error('Contrast check failed:')
    failures.forEach((f) => console.error(`  - ${f}`))
    process.exit(1)
  }

  console.log('✓ All contrast ratios pass WCAG criteria')
}

접근성 테스트 자동화

axe-core 통합

// tests/accessibility.test.ts

import { axe, toHaveNoViolations } from 'jest-axe'
import { render } from '@testing-library/react'

expect.extend(toHaveNoViolations)

describe('Button Accessibility', () => {
  it('기본 버튼은 접근성 위반 없음', async () => {
    const { container } = render(
      <Button onClick={() => {}}>클릭</Button>
    )
    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })

  it('아이콘 버튼은 aria-label 필수', async () => {
    const { container } = render(
      <Button size="icon" aria-label="설정">
        <SettingsIcon />
      </Button>
    )
    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })
})

Playwright 접근성 테스트

// e2e/accessibility.spec.ts

import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'

test.describe('Homepage Accessibility', () => {
  test('전체 페이지 WCAG 2.1 AA 준수', async ({ page }) => {
    await page.goto('/')

    const results = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze()

    expect(results.violations).toEqual([])
  })

  test('키보드로 모든 인터랙티브 요소 접근 가능', async ({ page }) => {
    await page.goto('/')

    // Tab으로 순회
    const focusableElements = await page
      .locator('button, a, input, select, textarea, [tabindex="0"]')
      .all()

    for (const el of focusableElements) {
      await page.keyboard.press('Tab')
      await expect(el).toBeFocused()
    }
  })
})

AI 기반 접근성 자동화

2025~2026년에 나온 AI 접근성 도구는 기존 룰 기반 검사를 넘어 컨텍스트를 이해하고 수정 코드를 직접 제안하는 단계까지 왔습니다.

주요 도구

  • Deque axe DevTools: AI 자동 수정 제안으로 코드에 직접 적용 가능. NLP 기반 컨텍스트 라벨링과 영향도/빈도 기반 우선순위 엔진을 제공합니다.
  • BrowserStack Accessibility Issue Detection Agent (2025.10): 사람처럼 판단해 전통적 정적 분석이 놓치는 동적 인터랙션 이슈를 잡아냅니다.
  • mabl: AI 네이티브 에이전트가 기존 E2E 테스트를 접근성 테스트로 재활용해, 테스트를 따로 짜지 않고도 접근성 커버리지를 확보합니다.
  • Percy Visual Review Agent: 시각적 리뷰 시간을 3배 단축하고, 비의미적 변경(레이아웃 시프트, 안티앨리어싱 차이 등)을 40% 자동 필터링합니다.

도구 비교

도구접근 방식강점
axe DevToolsAI 수정 제안코드 직접 적용
BrowserStack Agent인간 지능 모방전통 방법이 놓치는 이슈
mablE2E 테스트 재활용기존 테스트 자산 활용
Percy AI시각적 리뷰비의미적 변경 필터링

자동화의 현재 한계

AI 접근성 도구는 웹 이슈의 약 60%, 모바일 이슈의 30% 미만을 감지합니다. 복잡한 인터랙션 패턴, 인지 접근성, 실제 보조 기술 호환성은 여전히 수동 테스트가 필요합니다. 완전 자동화는 아직 불가능합니다. AI 도구는 전문가 리뷰를 보조하는 역할에 둬야 합니다.

체크리스트

참고 자료

  • WAI-ARIA Authoring Practices
  • WCAG 2.1 Guidelines
  • Axe Core
  • Inclusive Components
  • A11y Project Checklist

관련 문서

실행 플레이북

토큰(1주) → 컴포넌트 명세·접근성(2주) → AI 문서(3주) → 품질 게이트·변경 관리(4주)로 AI-First 디자인 시스템을 4주 만에 운영 가능 상태로 만드는 최소 경로.

비주얼 시스템 예시

브랜드 설계의 원칙 · B2B SaaS·소비자 앱·프리미엄 세 가지 유형별 컬러·타이포 팔레트와 8pt 그리드, 접근성 대비, 다크 모드, 모션 타이밍까지 그대로 참고할 수 있는 비주얼 시스템 레퍼런스.

Ch8. 테스트 전략

엔터프라이즈 프로젝트 설계 · 모노레포 환경 테스트 피라미드, Vitest 유닛, Playwright e2e

컴포넌트 명세

Props 스키마, 상태 머신, 슬롯 합성, 접근성을 명시하고 ComponentMeta를 단일 소스로 두어 AI가 정확한 코드를 생성하게 하는 컴포넌트 명세 설계법.

컴포넌트 명세

Props 스키마, 상태 머신, 슬롯 합성, 접근성을 명시하고 ComponentMeta를 단일 소스로 두어 AI가 정확한 코드를 생성하게 하는 컴포넌트 명세 설계법.

프롬프트 인터페이스

When to/NOT to Use, Props 테이블, Minimal→Complex 예시 중심의 컴포넌트 문서 포맷과 JSON 메타데이터·MCP 서버로 AI가 정확한 컴포넌트를 선택하게 하는 문서화 전략.

On this page

접근성 내장 전략ARIA 패턴 표준화역할(Role) 매핑상태(State) 속성 패턴관계(Relationship) 속성키보드 인터랙션 패턴표준 키보드 패턴포커스 트랩 구현스크린리더 전용 텍스트VisuallyHidden 컴포넌트Live Region 패턴색상 대비 검증대비 비율 기준토큰 기반 대비 보장자동 대비 검사 스크립트접근성 테스트 자동화axe-core 통합Playwright 접근성 테스트AI 기반 접근성 자동화주요 도구도구 비교체크리스트ARIA키보드시각스크린리더테스트참고 자료