본문 바로가기
접근성

[접근성 UI] 웹접근성을 위한 Focus Trap Modal

by 타로 스토리 2023. 5. 28.
반응형

 

웹접근성 UI제작을 위해서는 반드시 키보드 이벤트를 추가해야 합니다.

시각장애인을 비롯해서 신체부위를 자유롭게 사용하지 못하는 분들이 웹사이트를 제대로 이용하도록 하기 위해서입니다.

 

사이트 브라우징을 하기 위해 가장 많이 사용하는 키가 탭키입니다. 스페이스키와 엔터키, ESC 키도 많이 사용되지만 압도적으로 탭키를 많이 사용합니다.

 

탭키를 통해 페이지를 브라우징 하게 되는데, 현재 보고 있는 페이지를 벗어나 버리면 안되는 경우가 있습니다.

바로 모달창 입니다. 예전에는 팝업이라고 해서 브라우저를 하나 새로 띄워 내용을 보이도록 했지만 지금은 한 화면에서 레이어를 띄워 관련 내용을 팝업형태로 만듭니다.

 

그러다 보니 탭키를 통해 화면을 탐색하는 장애인분들에게 모달창에서 벗어나는 선택권을 드리기 위해 탭키가 모달창을 벗어나지 못하게 해야합니다. 그렇지 않으면 탭키를 통해 모달창을 띄운 화면으로 포커스가 이동해 현재 탐색하는 영역이 달라지게 됩니다.

 

접근성을 위해서는 포커스 이동도 사용자가 원하는 대로 이동해야 합니다.

포커스트랩이 적용된 모달을 만드는 방법입니다.

 

아주 기본적인 형태의 모달을 하나 만들었습니다.

<!-- 모달창을 여는 버튼 -->
<div>
  <button type="button" class="modal-show">Modal Open</button>
</div>

<!-- 모달창 -->
<div class="modal-layer">
  <div class="modal-dimm">
    <div class="modal">
      <div class="modal-header">
        <h1 tabindex="0">Modal Title</h1>
      </div>
      <div class="modal-contents">
        <p><a href="#">Modal content goes here.</a></p>
        <br />
        <div><input type="text" /></div>
        <br />
        <div><label tabindex="0">체크박스</label><input type="checkbox" /></div>
      </div>
      <!-- 닫기 버튼 -->
      <div class="modal-footer">
        <button type="button" class="modal-hide">Close Modal</button>
      </div>
    </div>
  </div>
</div>

 

CSS도 작성해서 스타일을 적용합니다.

.modal-layer {  
  display: none;
}

.modal-dimm {
  position:fixed;
  inset: 0;
  display: flex;
  margin: auto;
  background-color: rgba(0,0,0, .5);
}

.modal {
  display: flex;
  flex-direction: column;  
  width: 600px;
  aspect-ratio: 6/4;
  margin: auto;
  background-color: #ffffff;
}
.modal-header {
  padding: 20px;
  border-bottom: 1px solid #dadada;
}
.modal-header h1 {
  display: inline-block;
  font-size: 30px;
}

.modal-contents {
  padding: 20px;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  padding: 20px;
  margin-top: auto;
}
.modal-show,
.modal-hide {
  width: 140px;
  height: 50px;
  background-color: #2196F3;
  border: none;
  border-radius: 3px;
  color: #ffffff;
  cursor: pointer;
}

.modal-show {
  background-color:#D81B60;
}

 

먼저 HTML을 DOM으로 작성합니다.

const modal = document.querySelector(".modal-layer");

// Modal Open Button
const showBtn = document.querySelector(".modal-show");

// Modal Close Button
const hideBtn = document.querySelector(".modal-hide");

 

버튼을 클릭했을때 Event를 작성합니다.

 

오픈된 모달 컨텐츠에 포커스를 주기 위해서 modalcontents상수에 .modal 클래스를 갖는 모달창을 정의합니다.

modalcontents에 포커스를 주기 위해서 포커스를 받을 수 있도록 tabindex값을 0으로 설정합니다. (다만 접근성을 위한 모달 HTML 마크업시에 기본으로 tabindex="0"을 설정해 두었다면 자바스크립에선 설정하지 않습니다.)

오픈 후 바로 포커스를 modalcontents.focus()로 설정합니다.

const modal = document.querySelector(".modal-layer");
const modalcontents = document.querySelector(".modal");

// Modal Open Button
const showBtn = document.querySelector(".modal-show");
showBtn.addEventListener("click", () => {
  modal.style.display = "block";
  modalcontents.setAttribute("tabindex", 0)
  modalcontents.focus();
});

// Modal Close Button
const hideBtn = document.querySelector(".modal-hide");
hideBtn.addEventListener("click", () => {
  modal.style.display = "none";
});

 

포커스가 가능한 요소들을 querySelectorAll 메서드를 사용해 nodelist로 만들어줍니다. document.querySelectorAll이 아니라 modal.querySelectorAll을 사용해서 포커스 요소들을 modal 안에서만 찾습니다. 추가적으로 더 필요한 요소가 있다면 CSS 선택자로 추가해 주시면 됩니다.

const focusableEls = modal.querySelectorAll(
  "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"
);

 

첫 포커스 요소와 마지막 포커스 요소를 변수에 할당합니다. 포커스가 밖으로 나가지 못하도록 첫번째와 마지막 포커스에 key event를 적용해야 합니다.

const firstFocusableEl = focusableEls[0];
const lastFocusableEl = focusableEls[focusableEls.length - 1];

 

포커스 트랩 이벤트 함수를 작성합니다.

event.key === 'Tab'을 통해 탭키가 키다운 되었을때 조건에 따라 포커스를 이동시킵니다.

탭키의 기본이벤트를 event.preventDefalut()시켜 포커스가 기본 탭키 이벤트에 따라 이동되는걸 막아줍니다.

const modalTrapFocus = (event) => {
  if (event.key === "Tab") {
    if (document.activeElement === lastFocusableEl) {
      firstFocusableEl.focus();
      event.preventDefault();
    } 
  }
};

 

접근성 기준을 충족시키기 위해 역방향 포커싱을 만들어 줍니다.

정방향으로 포커스가 이동할 때는 마지막 포커스 요소가 모달창 밖으로 포커스가 이동되는 마지막 포커스 요소가 되지만 역방향으로 이동할때는 첫번째 요소가 마지막 포커스 요소가 됩니다.

event.shiftKey를 조건문에 포함시켜 첫번째 포커스 요소의 다음 포커싱을 마지막 요소로 만들어 줍니다.

const modalTrapFocus = (event) => {
  if (event.key === "Tab") {
    if (document.activeElement === lastFocusableEl && !event.shiftKey) {
      firstFocusableEl.focus();
      event.preventDefault();
    } else if (document.activeElement === firstFocusableEl && event.shiftKey) {
      lastFocusableEl.focus();
      event.preventDefault();
    }
  }
};

 

함수를 keydown 이벤트에 이벤트 핸들러로 등록합니다. 

document.addEventListener("keydown", modalTrapFocus);

 

마지막으로 접근성을 위해 ESC키를 이벤트에 등록하여 모달창을 닫을 수 있도록 합니다.

접근성 점검시 이벤트에서 ESC키가 닫기, 감추기, 취소 등의 역할을 수행할 수 있도록 해야 합니다. 이벤트 핸들러 함수에 ESC 키에 대한 조건문을 추가해 줍니다. 만약 document가 아니라 modal에 이벤트 핸들러를 추가하게 되면 ESC키가 작동하지 않습니다. 

  if (event.key === "Escape") {
    modal.style.display = "none";
  }

 

모달창내 포커스 요소 리스트를 만들고 첫번째와 마지막의 포커스를 찾아 포커스를 이동시키는게 트랩 포커스의 핵심입니다.

 

접근성 심사를 처음 받을때 이게 뭐야? 하면서 당황했던 기억이 납니다.

접근성 위반 사항을 수정할 수 있는 시간이 7일뿐인데 듣도 보도 못했던 포커스 트랩을 만들어야 해서 꽤 당황했었습니다. 

이것과 반대로 게시판에 글을 쓰는 에디터를 탈출하는 기능도 상당히 당황스러웠던 수정사항이었습니다.

 

접근성 심사를 준비하시는 분들에게 조금이나마 도움이 되었길 바래봅니다. 

 

See the Pen Tab Key Trap Modal by UI Builder (@gmptmiiw-the-reactor) on CodePen.

 

 

반응형

댓글