가이드라인 2.1 키보드 접근성

키보드 접근성은 모든 웹 사이트 기능을 키보드 만으로 사용할 수 있도록 보장하는 중요한 지침입니다. 이 지침의 핵심은 마우스나 기타 포인팅 장치에 의존하지 않고도 모든 대화형 요소에 접근하고 조작할 수 있어야 한다는 것입니다. 예를 들어, 모든 버튼과 링크는 Tab
키로 이동할 수 있어야 하고, Enter
키로 클릭할 수 있어야 합니다. 또한, 팝업 창과 같은 특정 영역에 들어갔을 때, 사용자가 원할 때 자유롭게 나올 수 있어야 합니다.
키보드 접근성은 시각 장애인, 운동 능력이 제한된 사용자, 그리고 키보드를 선호하는 사용자들에게 특히 중요합니다. 이 지침은 키보드로 모든 기능에 접근 가능하게 만들기, 키보드 트랩 방지, 문자 키 단축키 제공 등의 세부 기준을 포함합니다.
1. 성공 기준 2.1.1 키보드
(레벨 A)
콘텐츠의 모든 기능은 개별 키 입력에 대한 특정 타이밍을 요구하지 않는 키보드 인터페이스를 통해 조작 가능해야 합니다. 쉽게 말하자면 엘레베이터 버튼을 누를 때의 상황과 비슷합니다. 엘리베이터 버튼은 빠르게 누르든, 천천히 누르든 상관없이 작동합니다. 누르는 속도나 타이밍에 관계없이 버튼의 기능이 실행됩니다. 웹사이트에서도 이와 같은 원칙이 적용되어야 합니다.
그러나 이 지침에도 아래와 같은 예외상황이 있습니다.
-
경로 의존적 입력
아이패드 앱 - 프로크리에이트 손글씨 인식 기능이나 그림 그리기 앱과 같이 사용자의 입력 경로가 중요한 경우입니다. 이런 기능들은 본질적으로 타이밍과 움직임에 의존하기 때문에 예외로 인정됩니다.
-
실시간 게임
리듬 게임 - 이지투온 빠른 반응 속도를 요구하는 액션 게임이나 리듬 게임 등은 게임의 본질적인 부분으로 타이밍이 중요하므로 예외가 될 수 있습니다.
-
실시간 시뮬레이션
운전 시뮬레이션 비행 시뮬레이터나 실시간 음악 연주 앱과 같이, 실제 세계의 타이밍을 모방해야 하는 경우도 예외입니다.
-
보안 관련 기능
2단계 인증 특정 시간 내에 입력을 요구하는 2단계 인증과 같은 일부 보안 기능은 보안상의 이유로 타이밍이 중요할 수 있습니다.
-
경쟁적 테스트
한컴타자연습 타이핑 속도 테스트와 같이, 속도가 평가의 핵심인 경우는 예외로 볼 수 있습니다.
이러한 예외 사항들은 기능의 본질적인 목적을 해치지 않는 선에서 최대한 접근성을 고려해야 합니다. 예를 들어, 게임의 경우 난이도 조절 옵션을 제공하거나, 타이밍에 덜 의존적인 대체 모드를 제공하는 등의 방법을 고려할 수 있습니다. 또한 이 성공기준에서는 마우스 입력이나 다른 입력 방법을 키보드 조작과 함께 제공하는 것을 금지하지 않습니다.
2. 성공 기준 2.1.2: 키보드 트랩 방지
(레벨 A)
키보드를 사용하여 페이지의 구성요소로 키보드 포커스를 이동할 수 있는 경우, 키보드만을 사용하여 해당 구성요소에서 포커스를 이동할 수 있어야 합니다.


위 사진은 마우스 오버 시 하위 메뉴가 나타나는 네비게이션바를 포함한 가상의 웹사이트입니다. 두번째 사진처럼 네비게이션 바에 마우스 오버를 했을 때 하위 메뉴가 나타나고 있습니다. 그러나 하나의 문제가 있습니다.

사진과 같이 키보드를 사용하면 네비게이션바에서 하위 메뉴로 진입이 불가능한 상황입니다.
웹사이트에서 키보드를 사용할 때, Tab
키나 화살표(←↑↓→)
키로 여러 요소들을 이동하며 탐색합니다. 이는 마치 건물 안에서 방을 옮겨 다니는 것에 비교할 수 있는데, 만약 드롭다운 메뉴와 같은 어떤 요소가 있다면 자유롭게 내부로 이동하고 Tab
키나 Esc
키 같은 일반적인 방법으로 그곳을 빠져나올 수 있어야 합니다.
하지만 때로는 복잡한 기능 때문에 일반적인 방법으로는 빠져나올 수 없는 경우가 있습니다. 이런 경우, 마치 특별한 출구를 사용해야 하는 것처럼, 사용자에게 포커스를 이동하는 방법을 명확히 알려줘야 합니다. 예를 들어, 이 메뉴에서 나가려면 Alt+F4를 누르세요
와 같은 안내를 제공해야 합니다.
3. 성공 기준 2.1.3: 키보드 - 예외 없음
(레벨 AAA)
이 기준은 웹사이트의 모든 기능이 키보드 만으로 조작 가능해야 하며, 사용자가 특정 시간 내에 키를 눌러야 하는 기능이 없어야 합니다. 잡한 키 조합을 요구하지 않아야 합니다. 단순히 Tab
, Enter
, 화살표 키
등으로 모든 기능을 조작할 수 있어야 합니다.
4. 성공 기준 2.1.4: 문자키 단축키
(레벨 A)
키보드 단축키가 문자(대소문자 포함), 구두점, 숫자 또는 기호 문자만 사용하여 콘텐츠에 구현된 경우, 다음과 같은 옵션 중 하나 이상을 제공해야 합니다.
-
끄기
사용자가 단축키를 완전히 비활성화할 수 있는 옵션을 제공합니다. 이를 통해 사용자는 단축키로 인한 예기치 않은 동작을 방지할 수 있습니다.
-
리맵
하나 이상의 비인쇄 키보드 키(예: Ctrl, Alt)를 포함하도록 단축키를 다시 설정하는 메커니즘이 있음
-
초점에서만 활성화
사용자 인터페이스 구성요소에 대한 키보드 단축키는 해당 구성요소에 포커스가 있을 때만 활성화됨
단축키가 일반적인 문자, 숫자, 또는 기호로만 구성되어 있을 때 문제가 발생할 수 있습니다. 예를 들어, 'M' 키를 눌러 메뉴를 열거나 'P' 키를 눌러 페이지를 인쇄하는 기능이 있다고 가정했을 때 이런 단축키는 편리할 수 있지만, 화면 읽기 프로그램을 사용하는 시각 장애인이나 운동 능력에 제한이 있는 사용자에게는 오히려 방해가 될 수 있습니다. 그들이 평문을 입력하려고 할 때 의도치 않게 단축키가 작동할 수 있기 때문입니다.
5. 프론트엔드 개발자를 위한 실제 적용 방법

5.1 키보드 내비게이션 구현

tabindex
속성을 통한 포커스 순서 관리focus
및blur
이벤트로 키보드 포커스 관리keydown
,keyup
이벤트로 키보드 동작 구현
<!-- html -->
<header>
<nav>
<h1>키보드 접근성 예시</h1>
<div
tabindex="0"
role="button"
aria-haspopup="true"
aria-expanded="false"
id="menuButton"
>
메뉴
</div>
<ul id="menu" style="display: none">
<li><a href="#">메뉴 항목 1</a></li>
<li><a href="#">메뉴 항목 2</a></li>
</ul>
</nav>
</header>
<!-- html -->
<header>
<nav>
<h1>키보드 접근성 예시</h1>
<div
tabindex="0"
role="button"
aria-haspopup="true"
aria-expanded="false"
id="menuButton"
>
메뉴
</div>
<ul id="menu" style="display: none">
<li><a href="#">메뉴 항목 1</a></li>
<li><a href="#">메뉴 항목 2</a></li>
</ul>
</nav>
</header>
// js
const menuButton = document.getElementById('menuButton');
const menu = document.getElementById('menu');
menuButton.addEventListener('click', toggleMenu);
menuButton.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleMenu();
}
});
function toggleMenu() {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
menuButton.setAttribute('aria-expanded', !isExpanded);
menu.style.display = isExpanded ? 'none' : 'block';
}
// js
const menuButton = document.getElementById('menuButton');
const menu = document.getElementById('menu');
menuButton.addEventListener('click', toggleMenu);
menuButton.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleMenu();
}
});
function toggleMenu() {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
menuButton.setAttribute('aria-expanded', !isExpanded);
menu.style.display = isExpanded ? 'none' : 'block';
}
5.2 키보드 트랩 방지

- 모달 열기/닫기 시 포커스 관리
- ESC 키를 통한 탈출 기능
- 포커스 이동 경로에 대한 명확한 설명
<!-- html -->
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
<!-- html -->
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
// js
const openModalButton = document.getElementById('openModal');
const modal = document.getElementById('modal');
const closeModalButton = document.getElementById('closeModal');
let shortcutEnabled = true;
let shortcutKey = 'o';
openModalButton.addEventListener('click', openModal);
closeModalButton.addEventListener('click', closeModal);
function openModal() {
modal.showModal();
closeModalButton.focus();
}
function closeModal() {
modal.close();
openModalButton.focus();
}
modal.addEventListener('close', function () {
openModalButton.focus();
});
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
// js
const openModalButton = document.getElementById('openModal');
const modal = document.getElementById('modal');
const closeModalButton = document.getElementById('closeModal');
let shortcutEnabled = true;
let shortcutKey = 'o';
openModalButton.addEventListener('click', openModal);
closeModalButton.addEventListener('click', closeModal);
function openModal() {
modal.showModal();
closeModalButton.focus();
}
function closeModal() {
modal.close();
openModalButton.focus();
}
modal.addEventListener('close', function () {
openModalButton.focus();
});
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
5.3 단축키 구현

- 사용자 정의 가능한 단축키 설정
- 단축키 활성화/비활성화 옵션
- 시각적 피드백과 ARIA 레이블 제공
<!-- html -->
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
<div id="shortcutSettings">
<h2>단축키 설정</h2>
<label>
<input type="checkbox" id="enableShortcuts" checked /> 단축키 활성화
</label>
<button id="remapShortcut">단축키 재설정</button>
</div>
<!-- html -->
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
<div id="shortcutSettings">
<h2>단축키 설정</h2>
<label>
<input type="checkbox" id="enableShortcuts" checked /> 단축키 활성화
</label>
<button id="remapShortcut">단축키 재설정</button>
</div>
// js
const enableShortcutsCheckbox = document.getElementById('enableShortcuts');
const remapShortcutButton = document.getElementById('remapShortcut');
let shortcutEnabled = true;
let shortcutKey = 'o';
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
function updateModalButtonText() {
openModalButton.textContent = `모달 열기 (Ctrl+${shortcutKey.toUpperCase()})`;
openModalButton.setAttribute(
'aria-keyshortcuts',
`Control+${shortcutKey.toUpperCase()}`,
);
}
enableShortcutsCheckbox.addEventListener('change', function () {
shortcutEnabled = this.checked;
});
remapShortcutButton.addEventListener('click', function () {
const newShortcut = prompt('새로운 단축키를 입력하세요 (단일 문자):');
if (newShortcut && newShortcut.length === 1) {
shortcutKey = newShortcut.toLowerCase();
updateModalButtonText();
alert(`단축키가 Ctrl+${shortcutKey.toUpperCase()}로 변경되었습니다.`);
}
});
updateModalButtonText();
// js
const enableShortcutsCheckbox = document.getElementById('enableShortcuts');
const remapShortcutButton = document.getElementById('remapShortcut');
let shortcutEnabled = true;
let shortcutKey = 'o';
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
function updateModalButtonText() {
openModalButton.textContent = `모달 열기 (Ctrl+${shortcutKey.toUpperCase()})`;
openModalButton.setAttribute(
'aria-keyshortcuts',
`Control+${shortcutKey.toUpperCase()}`,
);
}
enableShortcutsCheckbox.addEventListener('change', function () {
shortcutEnabled = this.checked;
});
remapShortcutButton.addEventListener('click', function () {
const newShortcut = prompt('새로운 단축키를 입력하세요 (단일 문자):');
if (newShortcut && newShortcut.length === 1) {
shortcutKey = newShortcut.toLowerCase();
updateModalButtonText();
alert(`단축키가 Ctrl+${shortcutKey.toUpperCase()}로 변경되었습니다.`);
}
});
updateModalButtonText();
5.4 전체코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>키보드 접근성</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
header {
background-color: #333;
color: white;
padding: 1rem;
margin-bottom: 1rem;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
#menuButton {
background-color: #4caf50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
#menu {
list-style-type: none;
padding: 0;
margin: 0;
background-color: #f1f1f1;
position: absolute;
top: 60px;
right: 20px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
#menu li a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
#menu li a:hover {
background-color: #ddd;
}
main {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
button {
background-color: #008cba;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
button:hover,
button:focus {
background-color: #005f7f;
}
.modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
}
.modal h2 {
margin-top: 0;
}
#shortcutSettings {
margin-top: 20px;
padding: 20px;
background-color: #e9e9e9;
border-radius: 5px;
}
#shortcutSettings h2 {
margin-top: 0;
}
label {
display: block;
margin-bottom: 10px;
}
input[type='checkbox'] {
margin-right: 10px;
}
:focus {
outline: 3px solid #4caf50;
outline-offset: 2px;
}
/* dialog 요소 스타일 */
dialog {
padding: 20px;
border: none;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
/* 모달 내부 스타일 */
#modal {
width: 300px;
}
#modal h2 {
margin-top: 0;
}
</style>
</head>
<body>
<header>
<nav>
<h1>키보드 접근성 예시</h1>
<div
tabindex="0"
role="button"
aria-haspopup="true"
aria-expanded="false"
id="menuButton"
>
메뉴
</div>
<ul id="menu" style="display: none">
<li><a href="#">메뉴 항목 1</a></li>
<li><a href="#">메뉴 항목 2</a></li>
</ul>
</nav>
</header>
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
<div id="shortcutSettings">
<h2>단축키 설정</h2>
<label>
<input type="checkbox" id="enableShortcuts" checked /> 단축키 활성화
</label>
<button id="remapShortcut">단축키 재설정</button>
</div>
<script>
const menuButton = document.getElementById('menuButton');
const menu = document.getElementById('menu');
const openModalButton = document.getElementById('openModal');
const modal = document.getElementById('modal');
const closeModalButton = document.getElementById('closeModal');
const enableShortcutsCheckbox =
document.getElementById('enableShortcuts');
const remapShortcutButton = document.getElementById('remapShortcut');
let shortcutEnabled = true;
let shortcutKey = 'o';
menuButton.addEventListener('click', toggleMenu);
menuButton.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleMenu();
}
});
function toggleMenu() {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
menuButton.setAttribute('aria-expanded', !isExpanded);
menu.style.display = isExpanded ? 'none' : 'block';
}
openModalButton.addEventListener('click', openModal);
closeModalButton.addEventListener('click', closeModal);
function openModal() {
modal.showModal(); // dialog 요소의 showModal 메서드 사용
closeModalButton.focus();
}
function closeModal() {
modal.close(); // dialog 요소의 close 메서드 사용
openModalButton.focus();
}
function updateModalButtonText() {
openModalButton.textContent = `모달 열기 (Ctrl+${shortcutKey.toUpperCase()})`;
openModalButton.setAttribute(
'aria-keyshortcuts',
`Control+${shortcutKey.toUpperCase()}`,
);
}
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
// dialog 요소의 'close' 이벤트를 이용해 모달이 닫힐 때 포커스 처리
modal.addEventListener('close', function () {
openModalButton.focus();
});
enableShortcutsCheckbox.addEventListener('change', function () {
shortcutEnabled = this.checked;
});
remapShortcutButton.addEventListener('click', function () {
const newShortcut = prompt('새로운 단축키를 입력하세요 (단일 문자):');
if (newShortcut && newShortcut.length === 1) {
shortcutKey = newShortcut.toLowerCase();
updateModalButtonText();
alert(`단축키가 Ctrl+${shortcutKey.toUpperCase()}로 변경되었습니다.`);
}
});
// 초기 버튼 텍스트 설정
updateModalButtonText();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>키보드 접근성</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
header {
background-color: #333;
color: white;
padding: 1rem;
margin-bottom: 1rem;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
#menuButton {
background-color: #4caf50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
#menu {
list-style-type: none;
padding: 0;
margin: 0;
background-color: #f1f1f1;
position: absolute;
top: 60px;
right: 20px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
#menu li a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
#menu li a:hover {
background-color: #ddd;
}
main {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
button {
background-color: #008cba;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
button:hover,
button:focus {
background-color: #005f7f;
}
.modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
}
.modal h2 {
margin-top: 0;
}
#shortcutSettings {
margin-top: 20px;
padding: 20px;
background-color: #e9e9e9;
border-radius: 5px;
}
#shortcutSettings h2 {
margin-top: 0;
}
label {
display: block;
margin-bottom: 10px;
}
input[type='checkbox'] {
margin-right: 10px;
}
:focus {
outline: 3px solid #4caf50;
outline-offset: 2px;
}
/* dialog 요소 스타일 */
dialog {
padding: 20px;
border: none;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
/* 모달 내부 스타일 */
#modal {
width: 300px;
}
#modal h2 {
margin-top: 0;
}
</style>
</head>
<body>
<header>
<nav>
<h1>키보드 접근성 예시</h1>
<div
tabindex="0"
role="button"
aria-haspopup="true"
aria-expanded="false"
id="menuButton"
>
메뉴
</div>
<ul id="menu" style="display: none">
<li><a href="#">메뉴 항목 1</a></li>
<li><a href="#">메뉴 항목 2</a></li>
</ul>
</nav>
</header>
<main id="main-content">
<h2>키보드 접근성 데모</h2>
<p>
이 페이지는 키보드 접근성의 여러 측면을 시연합니다. Tab 키를 사용하여
페이지를 탐색해보세요.
</p>
<button id="openModal" aria-keyshortcuts="Control+O">
모달 열기 (Ctrl+O)
</button>
</main>
<dialog id="modal" aria-labelledby="modalTitle">
<h2 id="modalTitle">모달 제목</h2>
<p>이것은 모달 내용입니다. ESC 키를 눌러 모달을 닫을 수 있습니다.</p>
<button id="closeModal">닫기</button>
</dialog>
<div id="shortcutSettings">
<h2>단축키 설정</h2>
<label>
<input type="checkbox" id="enableShortcuts" checked /> 단축키 활성화
</label>
<button id="remapShortcut">단축키 재설정</button>
</div>
<script>
const menuButton = document.getElementById('menuButton');
const menu = document.getElementById('menu');
const openModalButton = document.getElementById('openModal');
const modal = document.getElementById('modal');
const closeModalButton = document.getElementById('closeModal');
const enableShortcutsCheckbox =
document.getElementById('enableShortcuts');
const remapShortcutButton = document.getElementById('remapShortcut');
let shortcutEnabled = true;
let shortcutKey = 'o';
menuButton.addEventListener('click', toggleMenu);
menuButton.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleMenu();
}
});
function toggleMenu() {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
menuButton.setAttribute('aria-expanded', !isExpanded);
menu.style.display = isExpanded ? 'none' : 'block';
}
openModalButton.addEventListener('click', openModal);
closeModalButton.addEventListener('click', closeModal);
function openModal() {
modal.showModal(); // dialog 요소의 showModal 메서드 사용
closeModalButton.focus();
}
function closeModal() {
modal.close(); // dialog 요소의 close 메서드 사용
openModalButton.focus();
}
function updateModalButtonText() {
openModalButton.textContent = `모달 열기 (Ctrl+${shortcutKey.toUpperCase()})`;
openModalButton.setAttribute(
'aria-keyshortcuts',
`Control+${shortcutKey.toUpperCase()}`,
);
}
document.addEventListener('keydown', function (event) {
if (
shortcutEnabled &&
event.ctrlKey &&
event.key.toLowerCase() === shortcutKey
) {
event.preventDefault();
openModal();
}
});
// dialog 요소의 'close' 이벤트를 이용해 모달이 닫힐 때 포커스 처리
modal.addEventListener('close', function () {
openModalButton.focus();
});
enableShortcutsCheckbox.addEventListener('change', function () {
shortcutEnabled = this.checked;
});
remapShortcutButton.addEventListener('click', function () {
const newShortcut = prompt('새로운 단축키를 입력하세요 (단일 문자):');
if (newShortcut && newShortcut.length === 1) {
shortcutKey = newShortcut.toLowerCase();
updateModalButtonText();
alert(`단축키가 Ctrl+${shortcutKey.toUpperCase()}로 변경되었습니다.`);
}
});
// 초기 버튼 텍스트 설정
updateModalButtonText();
</script>
</body>
</html>