React에서 웹 접근성: 시맨틱 HTML, ARIA, 포커스 관리, 및 axe 테스트

발행: (2026년 4월 7일 PM 05:34 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

Semantic HTML First

보통 어려워 보이는 접근성 문제는 대부분 의미론적 HTML 문제에 불과합니다:

// Bad – div soup, no semantics

  
    Home
    About
  

// Good – semantic HTML, keyboard accessible automatically

  
    

      - Home

      - About

    

  

랜드마크 요소(````, , , , )는 스크린리더 사용자가 섹션 사이를 빠르게 이동할 수 있게 해줍니다.

ARIA: When and When Not To

ARIA 속성은 HTML만으로는 충분하지 않을 때 의미를 추가하지만, 동작을 추가하지는 않습니다 — 키보드 인터랙션은 직접 구현해야 합니다:

// Custom dropdown – requires ARIA + keyboard handling
function Dropdown({ label, options, value, onChange }) {
  const [isOpen, setIsOpen] = useState(false)
  const listboxId = useId()

  return (
    
       setIsOpen(!isOpen)}
        onKeyDown={(e) => {
          if (e.key === 'Escape') setIsOpen(false)
          if (e.key === 'ArrowDown') {
            setIsOpen(true)
            // focus first option
          }
        }}
      >
        {value || label}
      
      {isOpen && (
        

          {options.map(opt => (
             { onChange(opt.value); setIsOpen(false) }}
              tabIndex={0}
            >
              {opt.label}
            
          ))}
        

      )}
    
  )
}

Rule: 네이티브 HTML 요소가 필요를 충족한다면(````, , ) 그대로 사용하세요. ARIA는 커스텀 위젯을 위해 존재합니다.

Focus Management

// Dialog – trap focus inside when open
import { useEffect, useRef } from 'react'

function Dialog({ isOpen, onClose, children }) {
  const dialogRef = useRef(null)

  useEffect(() => {
    if (!isOpen) return

    // Focus the dialog on open
    dialogRef.current?.focus()

    // Trap focus
    const focusable = dialogRef.current?.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )
    const first = focusable?.[0] as HTMLElement
    const last = focusable?.[focusable.length - 1] as HTMLElement

    const trap = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return
      if (e.shiftKey ? document.activeElement === first : document.activeElement === last) {
        e.preventDefault()
        ;(e.shiftKey ? last : first)?.focus()
      }
    }

    document.addEventListener('keydown', trap)
    return () => document.removeEventListener('keydown', trap)
  }, [isOpen])

  return (
     e.key === 'Escape' && onClose()}
    >
      {children}
    
  )
}

Color Contrast and Visual Design

WCAG AA는 일반 텍스트에 대해 4.5:1, 큰 텍스트에 대해 3:1의 대비 비율을 요구합니다.

// Check contrast in your Tailwind config
// Gray‑500 on white: 3.95:1 – FAILS AA for normal text
// Gray‑700 on white: 8.59:1 – passes AAA

// Don't rely on color alone to convey information
// Bad: red = error (invisible to color‑blind users)
// Good: red + icon + error message text

  
  Email is invalid

Automated Testing with axe

npm install -D @axe-core/react jest-axe
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)

it('has no accessibility violations', async () => {
  const { container } = render()
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

CI 환경에서 axe를 실행해 사용자에게 도달하기 전에 회귀를 잡아냅니다.

AI SaaS Starter는 whoffagents.com에서 제공되며, 의미론적 HTML 구조, 모든 인터랙티브 요소에 대한 적절한 ARIA 레이블, 그리고 테스트 스위트에 통합된 axe‑core를 포함합니다. $99 일회성 결제.

0 조회
Back to Blog

관련 글

더 보기 »