개요
contenteditable을 접근 가능하게 만드는 것은 어렵습니다. 스크린 리더가 변경사항을 제대로 알리지 않을 수 있고, ARIA 속성이 존중되지 않을 수 있으며, 키보드 탐색이 일관되지 않을 수 있습니다. 이 가이드는 접근성 문제와 해결책을 다룹니다.
contenteditable의 접근성 도전과제:
- 스크린 리더가 콘텐츠 변경을 자동으로 알리지 않음
- ARIA 속성이 제대로 알려지지 않을 수 있음 (특히 Safari)
- 키보드 탐색이 일관되지 않을 수 있음
- 포커스 관리가 복잡함
- 서식 변경이 알려지지 않음
스크린 리더 지원
문제: contenteditable 영역에서 콘텐츠가 변경될 때(텍스트 입력, 삭제, 서식 적용), 스크린 리더가 이러한 변경사항을 사용자에게 알리지 않습니다. 이로 인해 보조 기술에 의존하는 사용자가 에디터에서 무슨 일이 일어나고 있는지 이해하기 어렵습니다.
// ❌ 나쁨: 콘텐츠 변경에 대한 알림 없음
<div contenteditable>
<!-- 스크린 리더에 변경사항이 알려지지 않음 -->
</div>
// ✅ 좋음: 알림을 위해 aria-live 영역 사용
<div contenteditable
role="textbox"
aria-label="리치 텍스트 에디터"
aria-live="polite"
aria-atomic="false">
<!-- 콘텐츠 -->
</div>
// 또는 별도의 라이브 영역 사용
<div id="announcements"
aria-live="polite"
aria-atomic="false"
class="sr-only">
</div>
function announceChange(message) {
const announcements = document.getElementById('announcements');
announcements.textContent = message;
// 알림 후 지우기
setTimeout(() => {
announcements.textContent = '';
}, 1000);
}// 예제: 서식 변경 알림
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'formatBold') {
e.preventDefault();
applyBold();
announceChange('굵게 서식이 적용되었습니다');
} else if (e.inputType === 'formatRemove') {
e.preventDefault();
removeFormatting();
announceChange('서식이 제거되었습니다');
}
});
// 예제: 선택 영역 변경 알림
document.addEventListener('selectionchange', () => {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
if (!range.collapsed) {
const text = range.toString();
const wordCount = text.split(/\s+/).filter(w => w.length > 0).length;
announceChange(`${wordCount}개 단어가 선택되었습니다`);
}
}
});ARIA 속성
문제: contenteditable 영역에 ARIA 속성(role, aria-label, aria-describedby)을 적용할 때, 스크린 리더가 이를 제대로 알리지 않을 수 있으며, 특히 Safari에서 그렇습니다. 접근성 정보가 손실됩니다.
// ❌ 나쁨: Safari에서 ARIA 속성이 알려지지 않을 수 있음
<div contenteditable role="textbox" aria-label="에디터">
<!-- ARIA 정보가 손실될 수 있음 -->
</div>
// ✅ 좋음: 여러 ARIA 속성 사용 및 스크린 리더 간 테스트
<div contenteditable
role="textbox"
aria-label="리치 텍스트 에디터"
aria-describedby="editor-help"
aria-multiline="true"
aria-haspopup="false"
tabindex="0">
<!-- 콘텐츠 -->
</div>
<div id="editor-help" class="sr-only">
텍스트 서식을 위해 키보드 단축키를 사용하세요. Ctrl+B는 굵게, Ctrl+I는 기울임꼴입니다.
</div>포커스 관리
문제: 페이지 로드 시 폼 입력을 자동으로 포커스하는 autofocus 속성은 contenteditable 요소에서 작동하지 않습니다. 페이지가 로드될 때 contenteditable 영역을 자동으로 포커스하는 내장 방법이 없습니다.
// ❌ 나쁨: contenteditable에서 autofocus가 작동하지 않음
<div contenteditable autofocus>
<!-- 자동으로 포커스를 받지 않음 -->
</div>
// ✅ 좋음: JavaScript를 사용하여 포커스
<div contenteditable id="editor">
<!-- 콘텐츠 -->
</div>
<script>
// 페이지 로드 시 포커스
window.addEventListener('load', () => {
const editor = document.getElementById('editor');
editor.focus();
});
// 또는 더 나은 타이밍을 위해 requestAnimationFrame 사용
requestAnimationFrame(() => {
editor.focus();
});
</script>라이브 알림
// 전용 라이브 영역 생성
<div id="live-region"
aria-live="polite"
aria-atomic="false"
class="sr-only">
</div>
// 변경사항 알림
function announce(message, priority = 'polite') {
const liveRegion = document.getElementById('live-region');
liveRegion.setAttribute('aria-live', priority);
liveRegion.textContent = message;
// 알림 후 지우기
setTimeout(() => {
liveRegion.textContent = '';
}, 1000);
}
// 서식 알림
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'formatBold') {
announce('굵게 서식이 적용되었습니다');
} else if (e.inputType === 'formatItalic') {
announce('기울임꼴 서식이 적용되었습니다');
}
});
// 선택 영역 알림
document.addEventListener('selectionchange', debounce(() => {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
if (!range.collapsed) {
const text = range.toString();
const words = text.split(/\s+/).filter(w => w.length > 0);
announce(`${words.length}개 단어가 선택되었습니다`);
}
}
}, 300));스크린 리더 전용 CSS:
.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;
}플랫폼별 문제
브라우저 및 스크린 리더별 접근성 문제:
- Safari: contenteditable 요소의 ARIA 속성이 스크린 리더(VoiceOver)에 의해 제대로 알려지지 않을 수 있음. 해결책으로 라이브 영역 사용
- Chrome/Edge: 일반적으로 더 나은 ARIA 지원이지만, 동적 콘텐츠 변경에는 여전히 라이브 영역 사용
- Firefox: 키보드 탐색이 다르게 동작할 수 있음. NVDA로 철저히 테스트
- VoiceOver: 콘텐츠 변경을 자동으로 알리지 않을 수 있음. 모든 중요한 변경에
aria-live영역 사용 - NVDA: 일반적으로 더 나은 지원이지만, 여전히 라이브 영역으로 테스트