iBetter Books
수정

도입 스토리

"컴포넌트 라이브러리를 만들었는데, 어떻게 접근성을 보장하죠?" 김개발이 물었습니다. "모든 페이지에서 매번 테스트하기는 너무 많아요."

"Storybook에 a11y 애드온을 설치하면 컴포넌트 단위로 접근성을 검사하고 문서화할 수 있어요." 박멘토가 말했습니다. "Button 컴포넌트를 만들 때부터 접근성 패널에서 문제를 확인하는 거예요. 컴포넌트가 접근 가능하면 그걸 조합한 페이지도 기본적으로 접근 가능해집니다."

핵심 개념 설명

Storybook a11y 애드온 설치

npx storybook@latest initnpx storybook add @storybook/addon-a11y
// .storybook/main.jsmodule.exports = {  addons: [    '@storybook/addon-a11y',    // 기타 애드온...  ],};

설치 후 Storybook 하단에 "Accessibility" 패널이 추가됩니다. 각 스토리에서 실시간으로 axe 검사 결과를 확인할 수 있습니다.

접근성 스토리 작성

// Button.stories.jsx
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  parameters: {
    a11y: {
      // 특정 axe 규칙 설정
      config: {
        rules: [
          {
            id: 'color-contrast',
            enabled: true,
          },
        ],
      },
    },
  },
};

// 기본 버튼
export const Default = {
  args: {
    children: '저장',
    type: 'button',
  },
};

// 아이콘 버튼 (aria-label 필요)
export const IconOnly = {
  args: {
    'aria-label': '삭제',
    icon: 'trash',
  },
};

// 비활성 버튼
export const Disabled = {
  args: {
    children: '제출',
    disabled: true,
  },
};

// 로딩 버튼
export const Loading = {
  args: {
    children: '처리 중',
    'aria-busy': true,
    disabled: true,
  },
};

접근성 테스트 자동화 (Storybook Test)

// Button.test.js — Storybook + Jestimport { composeStories } from '@storybook/react';import { axe } from 'jest-axe';import * as stories from './Button.stories';const { Default, IconOnly, Disabled } = composeStories(stories);describe('Button 접근성', () => {  test('기본 버튼', async () => {    const { container } = render(<Default />);    expect(await axe(container)).toHaveNoViolations();  });  test('아이콘 버튼 — aria-label 필수', async () => {    const { container } = render(<IconOnly />);    expect(await axe(container)).toHaveNoViolations();  });});

디자인 토큰과 접근성

색상, 간격, 타이포그래피 등 디자인 토큰 수준에서 접근성을 보장합니다.

// tokens/colors.jsexport const colors = {  // 텍스트 색상 — 모두 WCAG AA (4.5:1) 이상  'text-primary': '#1a1a1a',       // 흰색 배경 대비: 16.1:1  'text-secondary': '#595959',     // 흰색 배경 대비: 7.0:1  'text-muted': '#767676',         // 흰색 배경 대비: 4.54:1  // 경계 없음: 흰색 배경에 4.5:1 미만 색상  // 'text-placeholder': '#999999' — 대비 2.85:1, WCAG 실패  // 인터랙티브 색상  'interactive-primary': '#005FCC',  // 흰색 배경 대비: 5.74:1  'interactive-hover': '#004499',    // 흰색 배경 대비: 8.59:1  'interactive-focus': '#005FCC',    // 포커스 링 색상  // 상태 색상 (색 + 텍스트/아이콘 함께 사용)  'status-error': '#CC0000',         // 오류 (빨간색 계열)  'status-success': '#007700',       // 성공 (초록색 계열)  'status-warning': '#CC6600',       // 경고 (주황색 계열)};
// tokens/typography.jsexport const typography = {  // 기본 텍스트 크기 (rem 단위)  'font-size-sm': '0.875rem',   // 14px  'font-size-base': '1rem',      // 16px  'font-size-lg': '1.125rem',   // 18px  // 줄 높이 (1.5 이상 유지)  'line-height-base': '1.6',  'line-height-heading': '1.3',  // 최소 폰트 크기: 0.75rem (12px) — 이하는 장식 목적만};

접근성 체크리스트 컴포넌트 문서화

디자인 시스템 문서에 컴포넌트별 접근성 가이드를 포함합니다.

// Button.mdx (Storybook CSF + MDX)
import { Meta, Story, Canvas } from '@storybook/blocks';

<Meta title="Components/Button" />

## 접근성 가이드

### 필수 구현 사항

| 시나리오 | 구현 방법 |
|---------|---------|
| 아이콘만 있는 버튼 | `aria-label` 속성 필수 |
| 토글 버튼 | `aria-pressed="true/false"` |
| 비활성 버튼 | `disabled` 또는 `aria-disabled` |
| 로딩 버튼 | `aria-busy="true"` + 텍스트 변경 |

### 금지 패턴

- `<div onClick>` — 반드시 `<button>` 사용
- `<a href="#">` — 버튼 동작에는 `<button>` 사용
- 색상만으로 상태 표시 — 텍스트 또는 아이콘 병행

<Canvas>
  <Story name="Default" />
</Canvas>

단계별 실습

따라하기: Storybook a11y 패널 사용

  1. Storybook을 실행합니다: npm run storybook
  2. Button 컴포넌트의 "IconOnly" 스토리를 클릭합니다.
  3. 하단 "Accessibility" 탭을 클릭합니다.
  4. aria-label 없이 아이콘 버튼을 렌더링하면 오류가 표시되는지 확인합니다.

변형하기: 새 컴포넌트에 a11y 스토리 추가

새 컴포넌트를 만들 때 접근성 시나리오를 스토리로 문서화합니다.

// Modal.stories.jsx
export const WithFocusTrap = {
  render: () => (
    <div>
      <button onClick={() => setOpen(true)}>모달 열기</button>
      <Modal open={open} onClose={() => setOpen(false)}>
        <h2>모달 제목</h2>
        <button onClick={() => setOpen(false)}>닫기</button>
      </Modal>
    </div>
  ),
  parameters: {
    docs: {
      description: {
        story: '모달이 열릴 때 포커스가 모달 안으로 이동하고, Escape로 닫힐 때 열기 버튼으로 복귀합니다.',
      },
    },
  },
};

정리와 확인

핵심 내용 요약

  • Storybook a11y 애드온: 컴포넌트 단위 axe 검사
  • 접근성 스토리: 각 사용 시나리오(아이콘 버튼, 비활성, 로딩)별 스토리
  • 디자인 토큰: 색상과 타이포그래피 수준에서 접근성 규칙 내재화
  • 컴포넌트 문서: 접근성 가이드와 금지 패턴을 Storybook MDX에 포함

확인 문제

문제 1. 디자인 토큰에서 text-muted: '#767676'을 사용하는 이유는?

#767676은 흰색 배경에서 대비율 4.54:1로 WCAG AA(4.5:1)를 충족합니다.
자주 실수하는 #999999는 2.85:1로 기준 미달입니다.
토큰에서 기준을 충족한 색상만 등록하면 사용자가 실수할 여지가 없습니다.

문제 2. 컴포넌트 레벨에서 접근성을 보장하는 것의 장점은?

페이지 레벨에서 매번 검사하는 것보다 효율적입니다.
컴포넌트가 접근 가능하면 조합된 페이지도 기본적으로 접근 가능합니다.
회귀 오류도 컴포넌트 수준 테스트에서 바로 잡을 수 있습니다.

다음 챕터에서는 CI/CD 파이프라인에 접근성 게이팅을 설정하는 방법을 다룹니다. PR이 머지되기 전에 접근성 기준을 통과해야 하는 파이프라인을 구축합니다.