크롬 브라우저가 102로 업데이트(22/05/24)되면서 변경 사항을 확인하다가 HTML 요소에 전역으로 넣을 수 있는 inert
속성에 대해서 접하게 되었다. inert
는 필자에게 생소한 속성명이라 무엇인지 알아보고 그 내용을 정리해 보았다.
inert의 사전적 의미는 '움직이지 않거나 움직일 수 없는' 라는 '비활성 상태의' 의미가 있다. HTML 요소에 inert
를 속성으로 사용하면 해당 요소를 비활성 상태로 만들겠다는 것이다.
그렇다면 노드가 비활성 상태(inert)일 때 어떻게 동작할까?
크롬 102 버전 업데이트 내용 중 inert 속성의 상세 설명 페이지에서는 다음과 같이 설명하고 있다.
pointer-events: none;
으로 설정된 것처럼 동작한다user-select: none;
으로 설정된 것처럼 동작한다inert
속성은 HTMLElement 모든 요소의 속성으로 사용할 수 있으며, 해당 요소가 마우스 클릭, 포커싱 이벤트와 텍스트 선택 같은 사용자와 상호 작용할 수 있는 이벤트 대상에 포함 되는지 여부를 부울 값으로 나타낸다.
다른 Boolean 속성과 동일하게, 요소에 inert
속성이 있으면 true
로 간주된다.
<div inert>...</div>
<!-- inert: true -->
<div>...</div>
<!-- inert: false -->
스크립트에서 동적으로 inert
속성을 설정할 수 있다.
isInert = HTMLElement.inert;
HTMLElement.inert = true | false;
// 또는
HTMLElement.setAttribute('inert', ''); // 요소에 inert 속성 적용
HTMLElement.removeAttribute('inert'); // 요소에 inert 속성 제거
요소에 inert
속성을 정의하면 마우스, 입력 이벤트를 무시하며, 보조 기술(assistive technology) 이벤트를 포함한 포커스 이벤트와 텍스트 선택을 무시한다.
보조 기술에 대한 설명은 이글에서 다루지 않는다. web.dev의 접근성 트리를 읽어보면 도움이 될 것이다.
inert
속성은 disabled
와 달리 브라우저가 기본으로 가지고 있는 스타일을 따로 변경하지 않는다. 시각적으로 구별되도록 스타일 속성을 변경할 수 있다.
[inert] > * {
opacity: 0.5;
}
inert
사용 이점그렇다면 inert
속성을 사용했을 때 이점은 무엇일까? inert
속성을 사용하면 스크린 리더 사용자에게 더 나은 접근성을 제공할 수 있다.
웹 콘텐츠 접근성 지침에서는 포커스 관리와 실용적이고 유용한 포커스 순서를 요구한다. 포커스 순서에는 탐색 기능(discoverability)과 상호 작용(interactivity)이 포함되는데, 접근성을 향상시키기 위해 aria-hidden="true"
를 요소에 설정하여 탐색 기능을 막을 수는 있지만 상호 작용은 제어하기 어렵다.
포커스 순서의 목적은 콘텐츠를 순차적으로 탐색할 때 콘텐츠의 의미와 일치하고 키보드에서 조작할 수 있는 순서로 정보를 접하게 하는 것이다. 웹 콘텐츠에서 순차 탐색 순서가 결정되는 방식은 콘텐츠의 기술에 의해 정의된다. 예를 들어, HTML은 탭 순서를 통해 순차 탐색을 정의하고 스크립트에서
tabindex
속성을 동적으로 추가해서 탐색 순서를 수정할 수 있다. 스크립트를 수정하거나tabindex
속성을 사용하지 않는 경우 탐색 순서는 구성요소가 콘텐츠에 나타나는 순서가 된다.
개발자는 inert
를 사용하여 요소를 탭 순서와 접근성 트리에서 제거할 수 있다. 이 기능을 통해 탐색 기능과 상호 작용을 모두 제어할 수 있으며, 사용자에게 더 나은 접근성을 제공할 수 있다.
더 나은 접근성을 위해 요소에 inert
속성을 적용하는 두 가지 주요 사용 사례는 다음과 같다.
웹 사이트에서 LNB 영역이 보였다가 닫히면 화면에 표시되지 않는 서랍형(drawer) UI를 접해본 적이 있다. inert
속성을 사용하면 LNB 영역이 화면 밖에 있을 때 사용자가 키보드로 상호작용할 수 없도록 한다.
서랍형 UI를 사용하는 간단한 예시를 작성해 보았다. 서랍형 UI인 "Drawer" 영역이 있고, 서랍형 UI를 열고 닫을 수 있는 토글 버튼이 있다. "Drawer" 영역 내부에는 상호 작용할 수 있는 입력 폼이 있다.
<body>
<header>
<input type="text" placeholder="Search..." />
</header>
<main>
<!-- 서랍형 UI -->
<aside class="drawer-container">
<div class="drawer">
<h1>Drawer</h1>
<form action="" method="get">
<fieldset>
<!-- 이름 입력 -->
<label for="name">Name: </label>
<input type="text" name="name" id="name" required />
</fieldset>
<fieldset>
<label for="email">Email: </label>
<input type="email" name="email" id="email" required />
</fieldset>
<button type="submit">Subscribe!</button>
</form>
</div>
<!-- 서랍형 UI를 열고 닫을 수 있는 토글 버튼 -->
<button type="button" class="toggle-btn"><</button>
<!-- '<' -->
</aside>
<!-- 메인 영역 -->
<section>
<p id="log"></p>
</section>
</main>
<script src="src/index.js"></script>
</body>
이름을 입력할 때마다 콘솔에 입력값이 로그로 남도록 이벤트를 등록했다.
const drawerContainer = document.querySelector('.drawer-container');
const toggleBtn = drawerContainer.querySelector('.toggle-btn');
const nameInput = drawerContainer.querySelector('#name');
toggleBtn.addEventListener('click', (e) => {
const isOpened = drawerContainer.classList.contains('drawer-closed');
drawerContainer.classList.toggle('drawer-closed');
toggleBtn.textContent = isOpened ? '<' : '>';
});
nameInput.addEventListener('input', (e) => {
console.log(`input name... ${e.target.value}`);
});
메인 영역에 키보드 이벤트가 발생하면 키 코드를 남기도록 작성했다.
const log = document.getElementById('log');
document.addEventListener('keyup', (e) => {
log.innerHTML += ` ${e.code}<br>`;
});
서랍형 UI가 닫혀있는 상태에서 키보드를 사용해 포커스를 이동한 후 타이핑해 보았다. 콘솔에 입력한 글자가 찍히고 있었다. 😕
서랍형 UI가 닫힌 상태에서는 inert
속성을 추가해주었다.
const drawer = drawerContainer.querySelector('.drawer');
toggleBtn.addEventListener('click', (e) => {
// ...
drawer.inert = !isOpened;
});
키보드 이벤트가 발생하여도 요소에 입력되는 값은 없다. 페이지에서 닫힌 서랍형 UI에 inert
속성을 추가하면, 스크린 리더의 포커싱 대상에서 서랍형 UI는 제외되며 사용자가 실수하더라도 화면에 보이지 않는 영역에서 상호 작용이 제한될 것이다.
페이지 상에서는 표시되지만, 사용자와 상호 작용할 수 없는 콘텐츠를 다룰 때 활용할 수 있다. 예를 들면 모달 같은 대화상자가 열려있을 때는 배경에 있는 콘텐츠들을 선택할 수 없다.
사용자에게 좋은 경험을 제공하기 위해 전체 페이지에서 사용자와 상호 작용할 수 있는 대화형 UI 요소 부분에 포커스를 맞추도록 한다. 이를 "포커스 트래핑"이라 한다.
포커스 트래핑은 좋은 UI 접근성의 핵심 개념이다. 스크린 리더는 포커스가 대화형 UI 요소에 있는지 확인하고 요소가 대화형 UI에서 더 이상 상호 작용하지 않는 상태로 변경됨을 인식한다.
inert
를 사용하면 탐색 가능한 콘텐츠에만 접근할 수 있으며, 다음과 같은 경우에 유용하다.
모달 UI를 띄우는 간단한 테스트 페이지를 만들었다.
<!-- 메인 영역 -->
<main>
<h1>Main Page</h1>
<input type="text" name="name" id="search" placeholder="Search..." />
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Architecto recusandae amet, rerum quasi
quaerat ipsum nam earum vitae distinctio deleniti. Rerum earum excepturi ab, totam tenetur fuga
praesentium illum repudiandae.
</p>
<!-- ... --->
<!-- 모달 UI를 여는 "Open Modal" 버튼 -->
<button class="open-modal-btn">Open Modal</button>
</main>
"Open Modal" 버튼을 클릭하면 아래와 같은 "Join Form"이라는 가입 양식을 포함한 모달 UI가 열린다.
HTML <dialog> 요소를 사용할 수 있지만 여기서 사용된 예시는 일반
<div>
요소로 작성해 주었다.<dialog>
요소를 사용하면 탭 키를 사용한 포커싱 문제가 발생하지 않는다.
<!-- Modal UI -->
<div class="modal hidden">
<div class="modal-content">
<h1>Join Form</h1>
<form action="" method="get">
<fieldset>
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required />
</fieldset>
<fieldset>
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required />
</fieldset>
<button type="submit" value="Subscribe!">Submit</button>
</form>
<button aria-label="close" class="close-btn x">❌</button>
</div>
</div>
메인 영역의 "Open Modal" 버튼과 모달 UI의 닫기 버튼에 이벤트 리스너를 등록한다.
const main = document.querySelector('main');
const openModalButton = main.querySelector('.open-modal-btn');
const modal = document.querySelector('.modal');
const closeButton = modal.querySelector('.close-btn');
function displayModal() {
modal.classList.toggle('hidden');
}
openModalButton.addEventListener('click', displayModal);
closeButton.addEventListener('click', displayModal);
키보드 탭 키를 사용해 포커스를 이동해 보았다. "Join Form" 모달이 열려있는 상태이지만, 메인 영역의 검색 input
요소와 모달 UI를 여는 "Open Modal"버튼에도 포커싱되있는 것을 확인할 수 있다.
모달 UI가 열린 상태에서는 메인 영역에 inert
속성을 추가해 주었다.
function displayModal() {
// ...
main.inert = !modal.classList.contains('hidden');
}
키보드 탭키를 사용하여 포커스를 이동하면 페이지에서 열린 모달에서만 포커스가 이동되는 것을 확인할 수 있다.
페이지에서 열린 모달 UI을 제외하고 나머지 요소에 inert
속성을 적용하면 스크린 리더의 탐색 대상이 모달 UI로 제한할 수 있다.
inert
vs disabled
vs readonly
추가로, disabled
와 readonly
속성과의 차이점을 표로 정리해보았다.
inert | disabled | readonly | |
---|---|---|---|
지원 요소 | 전체 HTML 요소 | <button> , <fieldset> , <keygen> , <optgroup> , <option> , <select> , <textarea> , <input> |
text, search, url, tel, email, password, date, month, week, time, datetime-local, number 타입의 <input> , <textarea> |
내용 편집 유무 | X | X | X |
스타일 변경 유무 | X | O - 회색 음영 또는 연하게 표시 | X |
포커스 가능 유무 | X | X | O |
required 와 함께 사용할 때 폼 전송 유무 |
X | O | O |
텍스트 선택 유무 | X | O | O |
inert
를 범용적으로 사용하기에 아직은 무리가 있다. 하지만 프로덕트 지원 브라우저 범위가 크롬 최신 버전이면서 웹 접근성을 고려한다면 inert
속성을 한 번 사용해 보는 것도 좋으리라 생각된다.
Chrome | Edge | Firefox | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
102 | 102 | Nightly | X | X | 15.5 |
(버전 확인 날짜 : 22/06/03 기준)
aria-hidden="true"
를 사용하면 탐색과 스크린 리더와 다른 보조 기술 사용자에게 해당 요소를 숨길 수 있다. 하지만 이 요소 자손으로 포커싱 가능한(focusable) 요소가 존재한다면 키보드 사용자들은 포커싱 가능한 요소에 접근할 수 있는데 사용자에게 혼란을 줄 수 있다.
inert
는 브라우저 지원 범위가 좁아 일반적으로 사용하기에는 아직 한계가 있다. 하지만 요소를 탐색 불가능하거나 상호 작용하지 않도록 만들기 위해 개별적으로 요소의 tabindex
를 조절하거나 특정 HTML요소에 적용할 수 있는 disabled
속성 또는 CSS point-events
, user-select
사용하여 제어하기보다는 모든 HTML 요소에서 사용할 수 있는 inert
속성을 상호 작용하지 않는 전체 영역에 한꺼번에 적용하는 것이 더 편리할 것이다. 주의할 점은 inert
속성은 HTML 스타일을 따로 변경하지 않는다. 따라서 상호 작용하지 않는 요소에 [inert]
선택자를 활용해 스타일을 변경하면 사용자에게 더 좋은 사용성을 제공할 것이다. 마지막으로 inert
속성에 대한 폴리필 코드는 github WICG/inert에서 확인할 수 있다.