리스트로 돌아가기
웹 접근성(A11y), 모든 사용자를 위한 웹사이트 만들기 thumbnail

웹 접근성(A11y), 모든 사용자를 위한 웹사이트 만들기

WCAG 가이드라인부터 키보드 네비게이션, 스크린 리더 대응까지, 실무에서 바로 적용할 수 있는 웹 접근성 개선 방법을 체크리스트 형태로 정리했습니다.

2026-01-09 00:00

웹 접근성(A11y), 모든 사용자를 위한 웹사이트 만들기

안녕하세요! 포테코입니다.

웹 접근성(Accessibility, 줄여서 A11y)은 모든 사용자가 웹사이트를 사용할 수 있도록 보장하는 것이에요. 시각 장애, 청각 장애, 운동 장애 등 다양한 능력을 가진 사용자들이 동등하게 웹 콘텐츠에 접근할 수 있어야 해요.

이번 글에서는 WCAG 가이드라인실무에서 바로 적용할 수 있는 웹 접근성 개선 방법을 체크리스트 형태로 정리했어요. Next.js와 React 프로젝트에서 바로 활용할 수 있는 예시와 함께 설명할게요.

웹 접근성은 법적 의무이기도 하지만, 더 중요한 것은 모든 사용자가 웹사이트를 사용할 수 있도록 하는 것이에요. 접근성을 개선하면 SEO와 사용자 경험도 함께 개선돼요.

웹 접근성이 왜 중요한가?

웹 접근성은 단순히 "좋은 것"이 아니에요. 법적 요구사항이기도 하지만, 더 중요한 것은 모든 사용자가 웹사이트를 사용할 수 있도록 하는 것이에요.

웹 접근성이 중요한 이유는 다음과 같아요:

  • 법적 요구사항: 많은 국가에서 웹 접근성을 법적으로 요구해요
  • 더 넓은 사용자층: 접근성을 개선하면 더 많은 사용자가 웹사이트를 사용할 수 있어요
  • SEO 개선: 검색 엔진은 접근성이 좋은 사이트를 선호해요
  • 사용자 경험 향상: 모든 사용자에게 더 나은 경험을 제공해요

WCAG 가이드라인 이해하기

WCAG(Web Content Accessibility Guidelines)는 웹 접근성의 국제 표준이에요. 4가지 원칙으로 구성되어 있어요:

원칙설명핵심 개념
인지 가능성 (Perceivable)정보와 UI 요소를 사용자가 인지할 수 있어야 함대체 텍스트, 색상 대비, 텍스트 크기
조작 가능성 (Operable)UI 요소와 네비게이션이 조작 가능해야 함키보드 접근, 충분한 시간 제공, 발작 유발 방지
이해 가능성 (Understandable)정보와 UI 조작이 이해 가능해야 함읽기 쉬운 텍스트, 예측 가능한 동작, 오류 식별
견고성 (Robust)다양한 기술로 해석 가능해야 함유효한 HTML, 보조 기술 호환

WCAG는 A, AA, AAA 세 가지 수준으로 나뉘어요. 대부분의 웹사이트는 AA 수준을 목표로 하는 것이 좋아요. AAA는 매우 엄격한 기준이라 모든 콘텐츠에 적용하기 어려울 수 있어요.

실전 체크리스트

1. 시맨틱 HTML 사용

체크리스트:

  • 적절한 HTML5 시맨틱 태그 사용 (<header>, <nav>, <main>, <article>, <section>, <footer>)
  • 제목 계층 구조 올바르게 사용 (h1 → h2 → h3 순서)
  • 목록은 <ul>, <ol>, <li> 사용
  • 폼 요소에 적절한 <label> 연결

실전 예시:

// ✅ 좋은 예
<header>
  <nav aria-label="메인 네비게이션">
    <ul>
      <li><a href="/"></a></li>
      <li><a href="/about">소개</a></li>
    </ul>
  </nav>
</header>
 
<main>
  <article>
    <h1>제목</h1>
    <p>본문 내용</p>
  </article>
</main>
 
// ❌ 나쁜 예
<div>
  <div>
    <div></div>
    <div>소개</div>
  </div>
</div>

2. 이미지에 대체 텍스트 제공

체크리스트:

  • 모든 이미지에 alt 속성 추가
  • 장식용 이미지는 alt="" 사용
  • 정보를 전달하는 이미지는 명확한 설명 제공
  • 복잡한 이미지(차트, 그래프)는 추가 설명 제공

실전 예시:

// ✅ 좋은 예
<Image
  src="/hero.jpg"
  alt="Poteco Studio의 팀이 함께 작업하는 모습"
  width={1200}
  height={630}
/>
 
// 장식용 이미지
<Image
  src="/decoration.jpg"
  alt=""
  width={100}
  height={100}
/>
 
// ❌ 나쁜 예
<img src="/hero.jpg" />

3. 색상 대비 확보

체크리스트:

  • 텍스트와 배경의 대비 비율 4.5:1 이상 (일반 텍스트)
  • 큰 텍스트(18pt 이상)는 3:1 이상
  • 색상만으로 정보를 전달하지 않기

실전 예시:

/* ✅ 좋은 예 - 충분한 대비 */
.text-primary {
  color: #1a1a1a; /* 배경이 #ffffff일 때 대비 16.5:1 */
  background-color: #ffffff;
}
 
/* ❌ 나쁜 예 - 대비 부족 */
.text-light {
  color: #cccccc; /* 배경이 #ffffff일 때 대비 1.6:1 */
  background-color: #ffffff;
}

색상만으로 정보를 전달하지 마세요. 예를 들어, "빨간색 텍스트는 필수 항목입니다"라고 하지 말고, 아이콘이나 텍스트로도 표시해야 합니다.

4. 키보드 네비게이션 지원

체크리스트:

  • 모든 인터랙티브 요소에 키보드로 접근 가능
  • 포커스 순서가 논리적
  • 포커스 표시가 명확함
  • 키보드 트랩 방지

실전 예시:

// ✅ 좋은 예 - 키보드 접근 가능
<button
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  클릭하세요
</button>
 
// 모달에서 키보드 트랩 방지
useEffect(() => {
  const handleEscape = (e: KeyboardEvent) => {
    if (e.key === 'Escape') {
      onClose();
    }
  };
  
  document.addEventListener('keydown', handleEscape);
  return () => document.removeEventListener('keydown', handleEscape);
}, [onClose]);

5. ARIA 속성 활용

체크리스트:

  • 필요한 경우에만 ARIA 사용 (시맨틱 HTML이 우선)
  • aria-label로 명확한 레이블 제공
  • aria-describedby로 추가 설명 연결
  • aria-hidden으로 불필요한 요소 숨기기

실전 예시:

// ✅ 좋은 예
<button
  aria-label="메뉴 닫기"
  onClick={closeMenu}
>
  <CloseIcon aria-hidden="true" />
</button>
 
// 폼 오류 메시지
<label htmlFor="email">이메일</label>
<input
  id="email"
  type="email"
  aria-invalid={hasError}
  aria-describedby={hasError ? "email-error" : undefined}
/>
{hasError && (
  <span id="email-error" role="alert">
    올바른 이메일 형식을 입력해주세요
  </span>
)}

6. 폼 접근성

체크리스트:

  • 모든 입력 필드에 <label> 연결
  • 필수 항목은 required 속성과 시각적 표시
  • 오류 메시지를 명확하게 표시
  • 오류 필드로 자동 포커스 이동

실전 예시:

// ✅ 좋은 예
<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="name">
      이름 <span aria-label="필수 항목">*</span>
    </label>
    <input
      id="name"
      type="text"
      required
      aria-required="true"
      aria-invalid={errors.name ? 'true' : 'false'}
      aria-describedby={errors.name ? 'name-error' : undefined}
    />
    {errors.name && (
      <span id="name-error" role="alert">
        {errors.name}
      </span>
    )}
  </div>
</form>

7. 스크린 리더 대응

체크리스트:

  • 페이지 구조를 명확히 표시 (<main>, <nav> 등)
  • 스킵 링크 제공
  • 랜드마크 역할 명시
  • 동적 콘텐츠 변경 시 알림

실전 예시:

// ✅ 스킵 링크
<a href="#main-content" className="sr-only focus:not-sr-only">
  메인 콘텐츠로 건너뛰기
</a>
 
<main id="main-content">
  {/* 메인 콘텐츠 */}
</main>
 
// 동적 콘텐츠 변경 알림
<div role="status" aria-live="polite" aria-atomic="true">
  {notification && <p>{notification}</p>}
</div>

8. 애니메이션 및 모션

체크리스트:

  • 사용자 선호도에 따라 애니메이션 비활성화 (prefers-reduced-motion)
  • 발작을 유발할 수 있는 깜빡임 방지
  • 중요한 정보는 애니메이션 없이도 접근 가능

실전 예시:

/* ✅ 좋은 예 */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
 
/* 깜빡임 방지 */
.flash {
  animation: none; /* 3초에 3회 이상 깜빡이지 않도록 */
}

Next.js/React 프로젝트에서의 접근성

1. 접근성 테스트 도구

체크리스트:

  • 개발 중 ESLint 플러그인 사용 (eslint-plugin-jsx-a11y)
  • Lighthouse 접근성 감사 실행
  • 키보드만으로 전체 사이트 탐색 테스트
  • 스크린 리더로 테스트 (NVDA, JAWS, VoiceOver)

설치 및 설정:

pnpm add -D eslint-plugin-jsx-a11y
// .eslintrc.js
module.exports = {
  plugins: ['jsx-a11y'],
  rules: {
    'jsx-a11y/alt-text': 'error',
    'jsx-a11y/anchor-is-valid': 'error',
    'jsx-a11y/aria-props': 'error',
    // ... 기타 규칙
  },
};

2. 접근성 컴포넌트 라이브러리

추천 라이브러리:

  • Radix UI: 접근성을 고려한 헤드리스 컴포넌트
  • React Aria: Adobe의 접근성 컴포넌트 프리미티브
  • Reach UI: 접근성 우선 컴포넌트

실전 예시:

// Radix UI 사용 예시
import * as Dialog from '@radix-ui/react-dialog';
 
<Dialog.Root>
  <Dialog.Trigger>열기</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Overlay />
    <Dialog.Content>
      <Dialog.Title>제목</Dialog.Title>
      <Dialog.Description>설명</Dialog.Description>
      <Dialog.Close>닫기</Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

종합 체크리스트

HTML 구조

  • 시맨틱 HTML 태그 사용
  • 제목 계층 구조 올바르게 사용
  • 랜드마크 역할 명시

이미지 및 미디어

  • 모든 이미지에 적절한 alt 텍스트
  • 비디오에 자막 제공
  • 오디오에 전사본 제공

색상 및 대비

  • 텍스트 대비 비율 4.5:1 이상
  • 색상만으로 정보 전달하지 않기
  • 다크 모드 지원 고려

키보드 접근성

  • 모든 기능이 키보드로 접근 가능
  • 포커스 순서가 논리적
  • 포커스 표시가 명확함
  • 키보드 트랩 방지

폼 접근성

  • 모든 입력 필드에 레이블 연결
  • 필수 항목 명시
  • 오류 메시지 명확하게 표시
  • 오류 필드로 자동 포커스

ARIA 활용

  • 필요한 경우에만 ARIA 사용
  • aria-label로 명확한 레이블
  • aria-describedby로 추가 설명
  • 동적 콘텐츠 변경 시 알림

테스트

  • ESLint 접근성 규칙 적용
  • Lighthouse 접근성 감사
  • 키보드만으로 탐색 테스트
  • 스크린 리더 테스트

실제 프로젝트 적용 예시

예시 1: 접근 가능한 버튼 컴포넌트

interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;
  ariaLabel?: string;
  disabled?: boolean;
}
 
export function AccessibleButton({
  children,
  onClick,
  ariaLabel,
  disabled = false,
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      aria-label={ariaLabel}
      aria-disabled={disabled}
      onKeyDown={(e) => {
        if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
          e.preventDefault();
          onClick();
        }
      }}
      className={clsx(
        'px-4 py-2 rounded',
        'focus:outline-none focus:ring-2 focus:ring-primary',
        disabled && 'opacity-50 cursor-not-allowed'
      )}
    >
      {children}
    </button>
  );
}

예시 2: 접근 가능한 모달

export function AccessibleModal({
  isOpen,
  onClose,
  title,
  children,
}: ModalProps) {
  useEffect(() => {
    if (isOpen) {
      // 모달이 열릴 때 포커스를 모달로 이동
      const modal = document.getElementById('modal');
      modal?.focus();
      
      // 배경 스크롤 방지
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = '';
    }
  }, [isOpen]);
 
  if (!isOpen) return null;
 
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      id="modal"
      tabIndex={-1}
      className="fixed inset-0 z-50"
    >
      <div
        className="fixed inset-0 bg-black/50"
        onClick={onClose}
        aria-hidden="true"
      />
      <div className="fixed inset-0 flex items-center justify-center p-4">
        <div className="bg-white rounded-lg p-6 max-w-md w-full">
          <h2 id="modal-title" className="text-xl font-bold mb-4">
            {title}
          </h2>
          {children}
          <button
            onClick={onClose}
            aria-label="모달 닫기"
            className="mt-4"
          >
            닫기
          </button>
        </div>
      </div>
    </div>
  );
}

접근성 테스트 도구

1. 자동화 테스트

  • axe DevTools: 브라우저 확장 프로그램
  • WAVE: 웹 접근성 평가 도구
  • Lighthouse: Chrome DevTools 내장

2. 수동 테스트

  • 키보드만으로 탐색: Tab, Shift+Tab, Enter, Space, 화살표 키
  • 스크린 리더 테스트: NVDA(Windows), JAWS(Windows), VoiceOver(Mac)
  • 색상 대비 확인: WebAIM Contrast Checker

마무리

웹 접근성은 한 번에 완성되는 것이 아니에요. 지속적으로 개선해나가는 과정이에요.

접근성을 개선하면 모든 사용자에게 더 나은 경험을 제공할 수 있어요. 작은 것부터 시작해서 점진적으로 개선해나가는 것을 추천해요.

개발 초기부터 접근성을 고려하면 나중에 수정하는 것보다 훨씬 쉽고 효율적이에요.

다음 단계

  1. 현재 상태 파악: Lighthouse나 axe DevTools로 접근성 감사 실행
  2. 우선순위 정하기: 가장 큰 영향을 주는 항목부터 개선
  3. 점진적 개선: 한 번에 하나씩 접근성 개선
  4. 정기적 테스트: 새로운 기능 추가 시 접근성 테스트

추천 도구

  • axe DevTools: 브라우저 확장 프로그램으로 쉽게 테스트할 수 있어요
  • Lighthouse: Chrome DevTools에 내장된 접근성 감사
  • WAVE: 웹 접근성 평가 도구
  • NVDA: 무료 스크린 리더 (Windows)

웹 접근성은 모든 사용자를 위한 웹사이트를 만드는 핵심이에요. 앞으로도 웹 접근성 개선을 위한 실전 팁과 WCAG 가이드라인 적용 사례를 계속 공유할 예정이니 기대해 주세요!