ARIA: marquee role
“끊임없이 움직이는 주식 티커, 스크린 리더는 어떻게 읽을까?”
주식 정보 관련 내용을 만들면서 이런 고민을 했습니다. 화면 상단에 실시간으로 스크롤되는 주식 정보가 있는데, 이걸 스크린 리더 사용자에게 어떻게 전달해야 할까요?
처음에는 role="status"를 사용했습니다. 하지만 1초마다 업데이트되는 주식 정보를 스크린 리더가 계속 읽어주니, 사용자가 다른 콘텐츠를 전혀 읽을 수 없었습니다.
스크린 리더: “코스피 559.22, 달러 1430.80, 유로 1668.03, 코스피 559.21, 달러…” 사용자: “아니, 다른 것들 좀 읽게 해줘!”
이러한 문제에 대한 대안으로 role="marquee"을 적용하였습니다.
이 글에서는 끊임없이 변경되는 비필수 정보를 접근 가능하게 만드는 방법을 알아봅니다.
marquee role이란?
role="marquee"는 자주 변경되지만 필수적이지 않은 정보를 표시하는 Live Region입니다.
<section
role="marquee"
aria-label="실시간 주식 시장 정보"
>
<div class="stock-ticker">
<!-- 스크롤되는 주식 정보 -->
</div>
</section>
공식 정의 (W3C WAI-ARIA 1.0)
“A type of live region where non-essential information changes frequently.”
“자주 변경되는 비필수 정보를 표시하는 Live Region의 한 종류”
주요 특징
- 기본적으로 조용함:
aria-live="off"(기본값) - 비필수 정보: 사용자가 반드시 알 필요가 없는 콘텐츠
- 순서 무관: 정보의 순서가 중요하지 않음
marquee vs log vs status - 차이점 이해하기
Live Region에는 여러 종류가 있습니다. 언제 무엇을 써야 할까요?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- marquee: 비필수 정보, 순서 무관 -->
<div role="marquee" aria-label="주식 정보">
코스피 559.22 | 달러 1,430.80 | 유로 1,668.03
</div>
스크린 리더: (읽지 않음 - aria-live="off")
사용 예: 주식 티커, 광고 배너, 스포츠 점수
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- log: 필수 정보, 순서 중요 -->
<div role="log" aria-label="채팅 메시지">
<div>사용자1: 안녕하세요</div>
<div>사용자2: 반갑습니다</div>
</div>
스크린 리더: (새 메시지를 읽어줌 - aria-live="polite")
사용 예: 채팅, 로그 기록, 히스토리
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- status: 상태 업데이트 -->
<div role="status" aria-live="polite">
저장되었습니다
</div>
스크린 리더: (현재 읽기가 끝난 후 알림)
사용 예: 저장 완료, 진행률, 알림
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- alert: 긴급 알림 -->
<div role="alert" aria-live="assertive">
세션이 1분 후 만료됩니다!
</div>
스크린 리더: (즉시 중단하고 읽음)
사용 예: 오류, 경고, 긴급 알림
선택 가이드
| 역할 | 정보 중요도 | 순서 | aria-live | 사용 예 |
|---|---|---|---|---|
marquee |
비필수 | 무관 | off |
주식 티커, 광고 |
log |
필수 | 중요 | polite |
채팅, 로그 |
status |
중요 | 무관 | polite |
상태 업데이트 |
alert |
긴급 | 무관 | assertive |
오류, 경고 |
핵심 질문: “사용자가 이 정보를 놓쳐도 괜찮은가?”
- 예:
marquee사용 - 아니오:
log또는status사용
필수 속성
1. 접근 가능한 이름 (Required)
marquee는 반드시 접근 가능한 이름을 가져야 합니다.
✅ aria-label 사용:
<section
role="marquee"
aria-label="실시간 주식 시장 정보"
>
<!-- 콘텐츠 -->
</section>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ aria-labelledby 사용 (화면에 보이는 레이블이 있을 때):
<section
role="marquee"
aria-labelledby="ticker-label"
>
<h2 id="ticker-label" class="sr-only">
실시간 주식 시장 정보
</h2>
<!-- 콘텐츠 -->
</section>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ 레이블 없음:
<section role="marquee">
<!-- 콘텐츠 -->
</section>
→ 스크린 리더: "영역" (무슨 영역인지 모름)
2. aria-live (Optional, Default: off)
기본값은 off지만, 필요에 따라 변경할 수 있습니다.
<!-- 기본: 읽지 않음 (권장) -->
<div role="marquee" aria-label="광고">
<!-- 광고 배너 -->
</div>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- polite: 중요한 업데이트는 알림 (선택적) -->
<div
role="marquee"
aria-live="polite"
aria-label="중요 공지사항"
>
<!-- 중요한 티커 -->
</div>
주의: aria-live="polite" 또는 assertive를 사용하면 marquee의 본래 목적과 맞지 않을 수 있습니다. 이 경우 role="status" 또는 role="log"를 고려하세요.
실전 패턴
스포츠 점수판 (Sports Scores)
<section
role="marquee"
aria-label="실시간 스포츠 점수"
>
<div class="scores">
<div class="game">
<span class="team">팀 A</span>
<span class="score">2</span>
<span class="separator">:</span>
<span class="score">1</span>
<span class="team">팀 B</span>
</div>
<!-- 더 많은 경기... -->
</div>
</section>
접근성 고려사항
1. 일시 정지 기능 제공 (필수)
WCAG 2.1 성공 기준 2.2.2 (일시 정지, 중지, 숨김):
“움직이거나, 깜박이거나, 스크롤되거나, 자동으로 업데이트되는 정보는 사용자가 일시 정지, 중지 또는 숨길 수 있어야 합니다.”
<!-- 방법 1: 호버/포커스 시 자동 일시 정지 -->
<style>
.marquee-content {
animation: scroll 30s linear infinite;
}
.marquee-container:hover .marquee-content,
.marquee-container:focus-within .marquee-content {
animation-play-state: paused;
}
</style>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- 방법 2: 명시적 일시 정지 버튼 -->
<section role="marquee" aria-label=정보">
<button
id="pause-btn"
aria-label="티커 일시 정지"
aria-pressed="false"
>
⏸ 일시 정지
</button>
<div class="tmarqueeicker-content">
<!-- 콘텐츠 -->
</div>
</section>
<script>
const pauseBtn = document.getElementById('pause-btn');
const marquee = document.querySelector('.marquee-content');
pauseBtn.addEventListener('click', () => {
const isPaused = pauseBtn.getAttribute('aria-pressed') === 'true';
if (isPaused) {
marquee.style.animationPlayState = 'running';
pauseBtn.setAttribute('aria-pressed', 'false');
pauseBtn.textContent = '⏸ 일시 정지';
} else {
marquee.style.animationPlayState = 'paused';
pauseBtn.setAttribute('aria-pressed', 'true');
pauseBtn.textContent = '▶ 재생';
}
});
</script>
2. 모션 감소 모드 지원 (필수)
@media (prefers-reduced-motion: reduce) {
.marquee-content {
animation: none !important;
/* 대신 수동 스크롤 가능하게 */
overflow-x: auto;
scroll-behavior: smooth;
}
}
3. 키보드 접근성
<!-- 각 티커 항목을 키보드로 탐색 가능하게 -->
<div
role="marquee"
aria-label="정보"
>
<div class="marquee-item" tabindex="0">
<span>아이템 1</span>
</div>
<div class="marquee-item" tabindex="0">
<span>아이템 2</span>
</div>
</div>
4. 색상만으로 정보 전달하지 않기
❌ 색상만으로 상승/하락 표시:
<span class="change" style="color: red;">-6.47</span>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 아이콘과 aria-label 추가:
<span class="change negative" aria-label="하락 6.47">
▼ -6.47
</span>
<span class="change positive" aria-label="상승 3.21">
▲ +3.21
</span>
상속받는 ARIA 속성
marquee는 region 역할을 상속받으므로 다음 속성들을 사용할 수 있습니다.
Live Region 관련
<!-- aria-atomic: 변경 시 전체를 읽을지, 일부만 읽을지 -->
<div
role="marquee"
aria-label="주식 정보"
aria-atomic="false"
>
<!-- false: 변경된 부분만 읽음 (기본값) -->
</div>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- aria-relevant: 어떤 변경을 알릴지 -->
<div
role="marquee"
aria-label="공지사항"
aria-relevant="additions text"
>
<!-- additions: 새로 추가된 노드 -->
<!-- text: 텍스트 변경 -->
</div>
일반 속성
<!-- aria-busy: 업데이트 중임을 표시 -->
<div
role="marquee"
aria-label="주식 정보"
aria-busy="true"
>
<!-- 로딩 중... -->
</div>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<!-- aria-hidden: 스크린 리더에서 숨김 -->
<div
role="marquee"
aria-label="광고"
aria-hidden="true"
>
<!-- 장식용 또는 중복 콘텐츠 -->
</div>
흔한 실수
❌ 실수 1: 중요한 정보에 marquee 사용
❌ 잘못됨:
<div role="marquee" aria-label="오류 메시지">
파일 업로드에 실패했습니다
</div>
→ 스크린 리더가 읽지 않아서 사용자가 오류를 모름!
✅ 올바름:
<div role="alert" aria-live="assertive">
파일 업로드에 실패했습니다
</div>
원칙: 사용자가 반드시 알아야 하는 정보는 alert 또는 status 사용.
❌ 실수 2: 레이블 없는 marquee
❌ 잘못됨:
<div role="marquee">
<!-- 콘텐츠 -->
</div>
→ 스크린 리더: "영역" (무슨 영역인지 모름)
✅ 올바름:
<div role="marquee" aria-label="실시간 뉴스 헤드라인">
<!-- 콘텐츠 -->
</div>
→ 스크린 리더: "실시간 뉴스 헤드라인 영역"
❌ 실수 3: 일시 정지 기능 없음
❌ 잘못됨:
<div role="marquee" aria-label="광고">
<div class="auto-scroll">
<!-- 계속 스크롤, 멈출 수 없음 -->
</div>
</div>
→ WCAG 2.1 위반! 사용자가 멈출 수 없음
✅ 올바름:
<div
role="marquee"
aria-label="광고"
class="ticker"
>
<div class="auto-scroll">
<!-- 호버/포커스 시 일시 정지 -->
</div>
</div>
<style>
.ticker:hover .auto-scroll,
.ticker:focus-within .auto-scroll {
animation-play-state: paused;
}
</style>
❌ 실수 4: aria-live를 assertive로 설정
❌ 잘못됨:
<div
role="marquee"
aria-label="주식 정보"
aria-live="assertive"
>
<!-- 1초마다 업데이트 -->
</div>
→ 스크린 리더가 계속 중단하며 읽어줌. 사용자 방해!
✅ 올바름:
<div
role="marquee"
aria-label="주식 정보"
aria-live="off"
>
<!-- 조용히 업데이트 (기본값) -->
</div>
예제: Score Board
테스팅
1. 스크린 리더 테스트
VoiceOver (Mac):
1. Cmd + F5로 VoiceOver 활성화
2. VO + U로 웹 로터 열기
3. 랜드마크 탭에서 "실시간 주식 시장 정보 영역" 확인
4. 티커 항목에 Tab으로 포커스 이동 가능한지 확인
NVDA (Windows):
1. Insert + F7로 요소 목록 열기
2. 랜드마크 섹션에서 marquee 영역 확인
3. Insert + Down Arrow로 탐색
2. 접근성 체크리스트
[ ] marquee에 aria-label 또는 aria-labelledby가 있는가?
[ ] aria-live가 off (기본값) 또는 적절한 값으로 설정되었는가?
[ ] 호버 또는 포커스 시 애니메이션이 일시 정지되는가?
[ ] prefers-reduced-motion을 지원하는가?
[ ] 티커 항목에 키보드로 접근 가능한가? (tabindex)
[ ] 색상만으로 정보를 전달하지 않는가? (아이콘 + aria-label)
[ ] 중복된 콘텐츠에 aria-hidden="true"가 있는가?
3. 자동화 테스트
// axe-core로 테스트
import { axe } from 'jest-axe';
test('marquee should be accessible', async () => {
const { container } = render(
<section role="marquee" aria-label="marquee 정보">
<div>콘텐츠</div>
</section>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
브라우저 및 스크린 리더 지원
| 스크린 리더 | 브라우저 | 지원 |
|---|---|---|
| NVDA | Chrome | ✅ |
| NVDA | Firefox | ✅ |
| JAWS | Chrome | ✅ |
| JAWS | Edge | ✅ |
| VoiceOver | Safari | ✅ |
| TalkBack | Chrome | ✅ |
언제 marquee를 사용하지 말아야 할까?
❌ 사용하지 말아야 하는 경우
- 중요한 정보: 사용자가 반드시 알아야 하는 내용
- 대신
role="alert"또는role="status"사용
- 대신
- 순서가 중요한 정보: 채팅, 로그, 히스토리
- 대신
role="log"사용
- 대신
- 사용자 액션이 필요한 정보: 폼 검증 오류, 확인 메시지
- 대신
role="alert"사용
- 대신
- 네비게이션 또는 메인 콘텐츠: 사이트의 핵심 기능
- 절대 marquee 사용 금지
✅ 사용해도 좋은 경우
- 주식 시장 티커
- 스포츠 점수판
- 광고 배너 (비침입적)
- 날씨 정보
- 뉴스 헤드라인 (보조적)
체크리스트: marquee를 올바르게 사용했나요?
[ ] 정보가 비필수적인가?
[ ] 정보의 순서가 중요하지 않은가?
[ ] aria-label 또는 aria-labelledby를 제공했는가?
[ ] aria-live="off"를 유지하는가? (또는 의도적으로 변경했는가?)
[ ] 사용자가 애니메이션을 일시 정지할 수 있는가?
[ ] prefers-reduced-motion을 지원하는가?
[ ] 키보드로 모든 콘텐츠에 접근 가능한가?
[ ] 스크린 리더로 테스트했는가?
마치며
role="marquee"는 “사용자가 놓쳐도 괜찮은 정보”를 표시하는 도구입니다.
처음에는 “모든 업데이트를 스크린 리더에 알려야 한다”고 생각했지만, 실제로는 그 반대였습니다. 너무 많은 알림은 오히려 접근성을 해칩니다.
marquee는 “조용히 업데이트하되, 사용자가 원할 때 접근할 수 있게” 만드는 역할입니다.
핵심 원칙:
- 비필수 정보에만 사용하세요
- 반드시 레이블을 제공하세요
- 사용자가 제어할 수 있게 하세요 (일시 정지)
- 스크린 리더로 테스트하세요
하나씩 적용하다 보면, 어느새 접근 가능한 Live Region을 마스터하게 될 겁니다! 🎉
참고 자료
공식 문서
- WAI-ARIA 1.0 Specification - marquee role - W3C 공식 명세
- ARIA: marquee role - MDN - MDN 가이드
- ARIA Live Regions - W3C - Live Region 속성 상세
접근성 가이드라인
- WCAG 2.1 - 2.2.2 Pause, Stop, Hide - 일시 정지 요구사항
- WCAG 2.1 - 2.3.3 Animation from Interactions - 모션 감소
- Understanding Success Criterion 2.2.2 - 시간 제한
테스팅 도구
- axe DevTools - ARIA 검증
- NVDA - 무료 스크린 리더 (Windows)
- VoiceOver - 내장 스크린 리더 (Mac)
관련 문서
- 📖 ARIA 기초 - ARIA 전반적인 이해
- 🔴 Live Regions 완전 가이드 - 동적 콘텐츠 접근성
- 📝 role=”log” - 순서가 중요한 업데이트
- ⚠️ role=”alert” - 긴급 알림
댓글