Tailwind CSS Academy · 강의

ARIA 속성과 스크린 리더

Tailwind의 sr-only 유틸리티를 aria-* HTML 속성과 결합하여 시각적 레이아웃에는 영향을 주지 않으면서 스크린 리더에 맥락을 제공합니다.

레슨 3/413개 단계

ARIA 속성과 스크린 리더은(는) CoddyKit의 무료 Tailwind CSS Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Tailwind CSS Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Tailwind CSS Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

How Screen Readers Work

Screen readers are assistive technology software that convert visual content into speech or braille output. They read the accessibility tree — a structured representation of the page derived from the DOM — rather than the visual layout. The accessibility tree includes element roles, names, states, and properties. Tailwind's visual classes affect the DOM, but to communicate correctly with screen readers, you also need proper HTML semantics and ARIA attributes.

Semantic HTML First

Before reaching for ARIA, use semantic HTML elements — they have built-in accessibility roles that screen readers understand without any extra attributes. A <button> is announced as 'button'; an <h1> as a heading level 1; a <nav> as a navigation landmark. Using <div> for everything requires adding ARIA to compensate. The first rule of ARIA is: use semantic HTML if a native element already provides the semantics.

<!-- BAD: div-based — requires ARIA to make accessible -->
<div class='flex items-center gap-2 cursor-pointer' onclick='handleClick()'>
  <span class='text-sm'>Submit</span>
</div>

<!-- GOOD: semantic button — accessible by default -->
<button
  type='submit'
  class='flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg'
>
  Submit
</button>

<!-- BAD: no landmark semantics -->
<div class='flex gap-4'>Nav links...</div>

<!-- GOOD: nav landmark -->
<nav class='flex gap-4' aria-label='Main navigation'>Nav links...</nav>

Tailwind's sr-only Utility

Tailwind's sr-only class visually hides content while keeping it accessible to screen readers. It uses a specific CSS technique: a 1×1 pixel clip with overflow hidden and absolute positioning that removes the element from visual layout but keeps it in the accessibility tree. Use sr-only for labels, descriptions, and context that screen reader users need but sighted users get from visual cues.

/* What .sr-only does */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

<!-- Usage: icon buttons need visible labels for screen readers -->
<button class='p-2 rounded-lg hover:bg-gray-100'>
  <XMarkIcon class='h-5 w-5 text-gray-700' />
  <span class='sr-only'>Close dialog</span>
</button>

ARIA Labels and Descriptions

aria-label provides an accessible name when there is no visible text label. Use it for icon-only buttons or elements whose visual label is insufficient. aria-labelledby references the ID of a visible element to use as the accessible name. aria-describedby references an element that provides supplementary context — like an error message or a hint below a form field — that is announced after the element's label.

<!-- aria-label for icon-only button -->
<button aria-label='Open notifications'
  class='p-2 rounded-lg hover:bg-gray-100'>
  <BellIcon class='h-5 w-5' />
</button>

<!-- aria-labelledby references visible heading -->
<section aria-labelledby='pricing-heading'>
  <h2 id='pricing-heading' class='text-2xl font-bold'>Pricing</h2>
  {/* screen reader: 'Pricing, region' */}
</section>

<!-- aria-describedby links to help text -->
<div>
  <label for='email'>Email</label>
  <input id='email' type='email' aria-describedby='email-hint' />
  <p id='email-hint' class='text-sm text-gray-500 mt-1'>
    We will never share your email.
  </p>
</div>

ARIA Live Regions

ARIA live regions announce dynamic content changes to screen readers without moving focus. Use aria-live='polite' for non-urgent updates (like search results loading) and aria-live='assertive' for urgent announcements (like error alerts). The content of the live region is announced when it changes. Tailwind classes style the visual container; the accessibility behavior comes entirely from the ARIA attribute.

<!-- Polite: announced after current announcement finishes -->
<div
  aria-live='polite'
  aria-atomic='true'
  class='sr-only'  {/* screen reader only — no visual display */}
>
  {searchStatus} {/* e.g., 'Loading results...' or '12 results found' */}
</div>

<!-- Assertive: interrupts current announcement -->
<div
  aria-live='assertive'
  role='alert'
  class='fixed top-4 right-4 bg-red-50 border border-red-200 p-4 rounded-lg'
>
  {errorMessage}
</div>

ARIA Roles for Custom Components

When you build custom interactive components with non-semantic HTML (like a div-based combobox or a custom toggle), add the appropriate ARIA role to communicate what the element is. Common roles include button, checkbox, combobox, listbox, option, tab, tabpanel, dialog, and alert. The role defines the implicit keyboard contract and what states are meaningful for that element type.

<!-- Custom toggle switch -->
<div
  role='switch'
  aria-checked={enabled}
  tabIndex={0}
  onKeyDown={(e) => e.key === 'Enter' && toggle()}
  onClick={toggle}
  class={cn(
    'relative inline-flex h-6 w-11 items-center rounded-full cursor-pointer',
    'focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2',
    enabled ? 'bg-blue-600' : 'bg-gray-300'
  )}
>
  <span class='sr-only'>{enabled ? 'Enabled' : 'Disabled'}</span>
  <span class={cn('inline-block h-4 w-4 rounded-full bg-white transition-transform',
    enabled ? 'translate-x-6' : 'translate-x-1'
  )} />
</div>

ARIA States and Properties

ARIA states (aria-checked, aria-expanded, aria-selected, aria-disabled) communicate the current interactive state of an element. Update these programmatically to keep the accessibility tree in sync with visual state. If a disclosure section expands, set aria-expanded='true' on the trigger. If a list item is selected, set aria-selected='true'. Screen readers announce these state changes as the user interacts.

function AccordionItem({ title, content }) {
  const [expanded, setExpanded] = useState(false);
  const contentId = 'accordion-content-' + title.replace(/\s/g, '-');

  return (
    <div class='border-b border-gray-200'>
      <button
        onClick={() => setExpanded(!expanded)}
        aria-expanded={expanded}
        aria-controls={contentId}
        class='flex w-full justify-between items-center py-4 text-left'
      >
        {title}
        <ChevronDownIcon class={cn('h-5 w-5 transition-transform', expanded && 'rotate-180')} />
      </button>

      <div
        id={contentId}
        hidden={!expanded}
        class='pb-4 text-gray-600 text-sm leading-relaxed'
      >
        {content}
      </div>
    </div>
  );
}

Hiding Decorative Content From Screen Readers

Not all visual content should be announced to screen readers. Decorative images, icon duplicates (when text already provides the meaning), and layout elements should be hidden from the accessibility tree. Use aria-hidden='true' to remove an element from the tree without hiding it visually. Never add aria-hidden='true' to elements that contain or are related to keyboard-focusable elements.

<!-- Decorative image: hide from screen reader -->
<img
  src='/decorative-pattern.svg'
  alt=''  {/* empty alt = decorative */}
  class='absolute inset-0 opacity-5'
  aria-hidden='true'
/>

<!-- Icon alongside text: icon is decorative -->
<button class='flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg'>
  <SaveIcon class='h-4 w-4' aria-hidden='true' />
  Save Changes  {/* text label is sufficient */}
</button>

<!-- Spinner: decorative, but announce loading state differently -->
<button disabled aria-busy='true'>
  <span aria-hidden='true' class='animate-spin'>...</span>
  <span class='sr-only'>Saving...</span>
</button>

Landmark Regions for Screen Reader Navigation

Landmark roles allow screen reader users to jump directly to major page sections. The HTML5 semantic elements <main>, <nav>, <header>, <footer>, <aside>, and <section> (with a name) automatically create landmark regions. When you have multiple <nav> elements, distinguish them with aria-label so screen reader users can navigate to the correct one.

<body class='min-h-screen flex flex-col'>
  {/* banner landmark */}
  <header class='bg-white border-b border-gray-200'>
    <nav aria-label='Main navigation' class='flex gap-6 px-6 h-16 items-center'>
      {/* nav links */}
    </nav>
  </header>

  <div class='flex flex-1'>
    {/* complementary landmark */}
    <aside class='w-64 border-r border-gray-200' aria-label='Sidebar'>
      <nav aria-label='Section navigation'>{/* sidebar links */}</nav>
    </aside>

    {/* main landmark */}
    <main class='flex-1 p-8'>{/* page content */}</main>
  </div>

  {/* contentinfo landmark */}
  <footer class='bg-gray-50 border-t border-gray-200 py-8'>
    {/* footer content */}
  </footer>
</body>

Visually Hidden Focus Announcements

Some UI patterns require screen reader announcements that have no visual counterpart. For example, after a form submits successfully, moving focus to a sr-only heading that reads 'Form submitted successfully' is a clean pattern that informs screen reader users without changing the visual UI. Combine sr-only with a tabIndex={-1} ref that receives programmatic focus.

// After form submit, announce result to screen readers
function SubmitForm() {
  const [submitted, setSubmitted] = useState(false);
  const statusRef = useRef(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    await submitForm();
    setSubmitted(true);
    // Move focus to the sr-only status message
    statusRef.current?.focus();
  };

  return (
    <form onSubmit={handleSubmit}>
      {submitted && (
        <p
          ref={statusRef}
          tabIndex={-1}
          class='sr-only'
        >
          Your form was submitted successfully. We will be in touch.
        </p>
      )}
      {/* form fields */}
      <button type='submit'>Submit</button>
    </form>
  );
}

Automated Screen Reader Testing

While there is no substitute for testing with a real screen reader (NVDA on Windows, VoiceOver on macOS/iOS, TalkBack on Android), automated tools catch many common issues. axe-core checks for missing ARIA labels, invalid role usage, and inaccessible color contrast. Integrate it into your CI pipeline using jest-axe for component tests or axe-playwright for end-to-end tests.

// jest-axe: test individual React components
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('Button has no accessibility violations', async () => {
  const { container } = render(
    <button class='px-4 py-2 bg-blue-600 text-white rounded'>
      Submit
    </button>
  );
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

test('Icon button has accessible label', async () => {
  const { container } = render(
    <button aria-label='Close dialog'>
      <XMarkIcon class='h-5 w-5' />
    </button>
  );
  expect(await axe(container)).toHaveNoViolations();
});

Quick Check

Test your understanding of Tailwind CSS Mastery concepts from this lesson.

Lesson Recap

In this lesson you learned: sr-only hides content visually while keeping it accessible to screen readers, ARIA labels, roles, and states augment custom components with accessibility semantics, and aria-live regions announce dynamic content changes without moving focus. Next up we apply all accessibility techniques to build accessible form components.

무료로 시작

AI 튜터와 함께 HTML을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
30
레슨
120

자주 묻는 질문

“ARIA 속성과 스크린 리더” 강의는 무료인가요?

네 — “ARIA 속성과 스크린 리더” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Tailwind CSS Academy 강의 전체를 잠금 해제할 수 있습니다. Tailwind CSS Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“ARIA 속성과 스크린 리더”에서 뭘 배우나요?

Tailwind의 sr-only 유틸리티를 aria-* HTML 속성과 결합하여 시각적 레이아웃에는 영향을 주지 않으면서 스크린 리더에 맥락을 제공합니다. 브라우저에서 직접 실행하는 실습 코드로 Tailwind CSS Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Tailwind CSS Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Tailwind CSS Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“ARIA 속성과 스크린 리더” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Tailwind CSS Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Tailwind CSS Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 색상 대비와 읽기 쉬운 텍스트
  2. 포커스 표시기와 키보드 탐색
  3. ARIA 속성과 스크린 리더
  4. 접근성 높은 폼 컴포넌트
← Tailwind CSS Academy(으)로 돌아가기