원문: https://inclusive-components.design/toggle-button/
본 번역문은 원작자의 동의하에 번역 및 게시되었습니다.
원문 게시일: 2017년 3월 31일
- 번역글 1차 수정: 2018년 1월 5일
토글 버튼
토글 버튼 중 어떤 것들은 껐다가 킬 수 있지만, 어떤 것들은 킬 수가 없기도(혹은 못 끄기도)하고, 어떤 것들은 반드시 꺼야만(혹은 반드시 켜야만) 합니다. 토글 버튼의 컨셉 자체는 너무나 간단합니다. 앞 문장은 제가 일부러 온오프 스위치, 즉 토글 버튼이라고 해서 모두 똑같지는 않다는 것을 알려드리기 위해 일부러 복잡하게 만들었을 뿐입니다. 토글 버튼의 용도는 간단하지만, 이것의 사용처나 형태는 매우 다양하게 나뉘어 졌습니다.
수용적인 컴포넌트(Inclusive Component) 시리즈의 첫 공식 포스트인 이 글에서, 어떻게 하면 수용적인 토글 버튼을 만들 수 있는지에 대해 탐구해 보도록 하겠습니다. 다른 모든 컴포넌트와 마찬가지로, 토글 버튼도 역시 만드는 방법이 하나로 딱 정해져 있지는 않습니다. 특히나 다양한 사용 맥락 하에 토글 버튼을 살펴본다면 말이죠. 만들면서 빼먹거나 자칫하면 컴포넌트를 망칠 수 있는 함정들이 많이 있기 때문에, 여기에 빠지지 않도록 한번 노력해 보도록 하겠습니다.
상태 바꾸기
만약 사용자가 내리는 지시사항에 맞추어 웹 애플리케이션의 상태가 바뀌지 않는다면 사용자가 최종적으로 느끼게 될 경험은 총체적으로 불만족스러워질 것입니다. 그렇다고는 하나, 지금으로써는 페이지 새로고침에 전혀 의지 하지 않고, 웹 문서를 순간순간 업데이트하는 사치를 항상 누릴 수는 없는 상황입니다.
불행하게도, 웹 페이지의 접근성을 향상 시키겠다고 마음먹고 난 이후로 실제로 향상이 된 곳은 오직 읽기를 목적으로 만들어진 정적인 문서들 한정으로, 접근성과 관련하여 손볼 여지가 매우 적은 곳들 입니다. 따라서 더 요소가 풍부하고, 상태 관리가 필요한 웹 애플리케이션에 우리가 쏟아 부은 노력은 매우 적다고 할 수 있습니다.
‘스크린 리더기가 자바스크립트를 이해하지 못한다‘라는 잘못된 인식이 널리 퍼지고 있습니다. 모든 주요 스크린 리더기기는 DOM에 변화가 발생할 때마다 이에 반응합니다. 이것 말고도 완전히 사실과 동떨어진 이야기들이 있습니다. 그러나 어찌되었든간에, 기본 상태 변화가 눈으로도 보여야 하고 보조 기술 소프트웨어에도 알려질 필요가 있다 하여도 자바스크립트에 꼭 의존할 필요는 없습니다.
체크박스와 라디오 버튼
폼 요소는 인터렉티브 웹 페이지를 이루는 기초적인 요소이며, 이 요소들을 우리가 직접적으로 사용하지는 않는 상황이더라도 동작 원리에 대해서는 신경써서 알아두어야만 합니다. 폼 요소들이 상태 변화를 다루는 방식은, 사용적인 측면에서 일종의 관습으로 굳어졌기 때문에 이를 무시한다면 어리석은 행동으로 비추어 질 수 있습니다.
단언컨대, 무적의 checkbox
타입 인풋 요소만 가지고서도 서비스 나갈 수준으로 완벽하게 온오프 스위치를 만들 수 있습니다. 레이블만 정확하게 붙인다면, 접근성을 띈 컨트롤이 가져야 할 기본적인 재료들은 다 갖춘 셈입니다. 플랫폼과 기기를 아우르는 스크린 리더기기와 키보드에 대응 가능한 접근성을 갖추고 있으면서, 웹 페이지를 전체 재빌드 할 필요 없이 상태 변화(checked와 unchecked 두 값 사이를 왔다갔다 하면서)를 알릴 수 있습니다.
다음은 이메일 알림 설정 토글 버튼 역할을 하는 체크박스에 대한 예시입니다.
<input type="checkbox" id="notify" name="notify" value="on"> <label for="notify">Notify by email</label>
스크린 리더기기 소프트웨어는 컨트롤을 대할 때, 상당한 일관성을 가지고 읽어들입니다 . 컨트롤에 포커스를 맞추면(탭키를 사용해 포커스를 움직이는 경우), “이메일 알림 받기, 체크박스, 선택안됨“과 유사한 알림이 나옵니다. 이 알림은 레이블, 역할, 상태의 순서로 현재 상태에 대한 정보를 담고 있습니다.
체크박스를 체크하면, 대부분의 스크린 리더기기 소프트웨어는 바뀐 상태에 대해 “체크됨(checked)”(어떨 때는 레이블과 역할 정보를 반복하기도 합니다)이라고 즉시 알림을 내보냅니다. 자바스크립트 없이도 상태를 변경하였고, 스크린 리더기기 소프트웨어를 통해 이에 대한 피드백을 사용자에게 돌려줄 수 있습니다.
NOTE
스크린 리더기기는 시력장애인만을 위한 것이 아닙니다
일부 사람들은 인터페이스에 대한 이해도를 더 높이기 위해 스크린 리더기를 사용합니다. 이 외에도 난독증을 앓고 있거나, 낮은 식자율을 지닌 사람들이 스크린 리더기를 사용합니다. 심지어 인터페이스 이해를 하는데 신체적이거나 인지적인 어려움을 거의 가지고 있지 않더라도, 스크린 리더기기가 화면을 크게 읽어 주기 때문에 가끔씩 그냥 사용하는 것을 좋아하는 사람들이 있습니다.
스크린 리더기 소프트웨어를 지원한다는 것은 말그대로 스크린 리더기 소프트웨어를 지원하는 것이지, 시력장애인을 지원하는 것이 아닙니다. 스크린 리더기기 다양한 많은 사람들이 즐겨 사용하는 도구로 봐야 합니다.
이 경우, 스위치의 꺼짐/켜짐 부분은 레이블로 전달이 되지 않고, 상태만 레이블로 전달이 되고 있는 중입니다. 레이블은 우리가 지금 무엇을 끄고 키고 있는지를 명백히 지정하기 위해 사용합니다. 만약 사용자에게 조금 더 명백한 켜짐/꺼짐 메타포를 알려야 할 필요가 있는 상황이라면, 라디오 버튼 그룹을 사용해 보시면 됩니다.
<fieldset> <legend>Notify by email</legend> <input type="radio" id="notify-on" name="notify" value="on" checked> <label for="notify-on">on</label> <input type="radio" id="notify-off" name="notify" value="off"> <label for="notify-off">off</label> </fieldset>
그룹 레이블은 강력한 도구입니다. 이름이 말해주듯이, 그룹 레이블을 사용하면 연관된(그룹 지어진) 아이템을 대표하는 레이블을 하나 붙여줄 수 있습니다. 이 경우, <fieldset>
그룹 요소는 <legend>
요소와 함께 “이메일 알림 받기” 그룹 레이블을 라디오 버튼 쌍에 붙이는 데 쓰이고 있습니다. 두 라디오 버튼은 쌍으로 name
속성값을 공유하기 위해 쌍으로 만들어졌습니다. 따라서 방향키를 사용해 두 버튼 사이를 토글할 수 있습니다. HTML 시멘틱은 단지 정보를 부여하는 데 그치는 것이 아니라, 행동에도 영향을 끼칩니다.
JAWS나 NVDA와 같은 윈도우 운영체제 스크린 리더기에서는 사용자가 첫번째 컨트롤에 초점을 맞추면, 그룹 레이블이 개별 컨트롤 레이블 앞에 붙게 되며, 그룹화된 라디오 버튼들은 하나씩 차례로 나열됩니다. NVDA에서는 “그룹으로 묶여있음(grouping)”이라는 용어를 붙여서 이 상황을 좀 더 명백하게 만듭니다. 위의 예제에서는, 첫번째 라디오 버튼(디폴트로 체크되어 있음)에 초점이 가면, “이메일 알림 받기, 그룹으로 묶여있음, 라디오 버튼에, 선택됨, 두번째 중 하나“라는 알림이 나옵니다.
이제는 심지어 체크된(checked) 상태(일부 스크린 리더기기들은 이것을 “선택됨(selected)”이라고 읽습니다)가 여전히 왔다갔다 하는 중이더라도, 우리가 사용자에게 실제로 허락한 일은 “켜짐”과 “꺼짐” 사이를 전환하도록 한 것입니다. 이 두 가지 상태는 컨트롤이 취할 수 있는 ‘어휘적'(lexical) 상태라고 부를 수 있을 것 같네요.
NOTE
폼 요소 스타일링하기
폼 요소는 스타일링이 어렵기로 악명이 높습니다. 그러나 브라우저 지원률이 좋은 라디오와 체크박스 컨트롤 스타일링 CSS 테크닉이 몇가지 있습니다. 제가 ‘라디오 버튼 대체하지 않고 라디오 버튼 대체하기‘ 글에 썼던 것 처럼요. 셀렉트 요소 파일 인풋 스타일에 관한 팁을 얻고 싶다면, 마크 오토의 ‘WTF Forms?‘를 참조하세요.
이건 별로 안 괜찮은 것 같은데요
체크박스와 라디오 버튼으로 구현한 토글 버튼은 모두 온오프 스위치 컨트롤 용으로 사용할 만 합니다. 어찌 되었든 두 요소 모두 모두 폭넓게 기기 및 브라우저, 운영체제 지원을 하면서 마우스, 손동작, 키보드, 보조 기기 소프트웨어에 대한 접근성을 보장하니깐요.
그러나 접근성은 수용적 디자인을 이루는 한 부분에 지나지 않습니다. 이 컨트롤은 사용자가 쓸 때도 말이 되게 만들어져야 합니다. 인터페이스 내에서 정체성의 모호함 없이 제 역할을 해내야 합니다.
폼 요소를 토글 버튼으로 사용하게 되면, 데이터 콜렉션과 폼 요소가 지속적인 연관성을 맺게 되므로 문제가 생깁니다. 체크박스나 라디오 버튼은 값을 받기 위한 컨트롤로 만들어졌습니다. 사용자가 체크박스를 클릭할 때 그냥 상태만 바꾼다고 생각할 수도 있으나, 여기에 더해 지금 ‘나는 폼 제출용 값을 고르고 있는게 아닌가’ 하는 미심쩍은 생각이 들 수도 있는 것입니다.
설령 여러분이 체크박스를 눈으로 직접 볼 수 있는 비시각장애인이라 하더라도, 스크린 리더 사용자는 요소의 정체가 리더기를 통해 낭독되는 것을 듣고 있을 터이므로, (혹은 여러분이 비시각장애인이면서 리더기를 사용할 수도 있죠) 어원적인 불일치가 발생할 수 있습니다. 우리는 토글 버튼이 버튼처럼 되기를 바라는데, 체크박스나 라디오 버튼은 사실 입력 요소이기 때문입니다.
진정한 토글 버튼
가끔가다 우리는 <button>
요소를 폼 제출용으로 사용합니다. 버튼이 충실히 제 역할을 수행하도록 만들려면, 버튼 요소에 type
속성값으로 submit
을 사용해야만 합니다.
<button type="submit">Send</button>
그러나 이는 오직 하나의 사용 시나리오를 고려하여 만든 예시일 뿐입니다. 사실, <button>
요소는 폼과 관련된 것 뿐만 아니라 다른 모든 종류의 일에 쓰일 수 있습니다. 그냥 버튼일 뿐이니깐요. type
속성값에 button
을 부여해 봄으로써, 다시 한번 이 말의 뜻을 되새겨 보도록 하겠습니다.
<button type="button">Send</button>
일반적인 버튼은 인터페이스 내에서 무언가 변화를 주고자 할 때 (자바스크립트를 사용하지만 페이지 재로딩이 일어나지 않도록 하려고 할 때) 가장 일반적으로 사용되는 요소입니다. 사용자가 웹 문서를 앞뒤로 넘나들 때를 제외하면요. 이런 일은 링크가 할 일입니다.
링크 다음으로 버튼은 여러분이 웹 애플리케이션에서 가장 많이 사용하는 인터렉티브 요소가 되어야 합니다. 버튼 요소에는 ARIA “button” 역할과 키보드 및 스크린리더기 접근성이 기본으로 내장되어 있습니다. 몇몇 다른 폼 요소와는 달리, 버튼은 스타일을 바꾸기도 쉽습니다.
자, 그래서 <button>
요소를 어떻게 토글 버튼으로 만들까요? 이때가 바로 WAI-ARIA를 ‘점진적인 향상’ 차원에서 사용할 때입니다. WAI-ARIA는 기본적인 HTML만으로는 알려주기 힘든 ‘상태’에 대한 정보를 제공할 수 있습니다. 예를 들면, 눌림(pressed) 상태 같은 것들 말입니다. 컴퓨터의 전원 스위치를 상상해보세요. 이것이 눌려있다면(또는 안으로 들어간 상태라면), 컴퓨터는가 “켜져 있음”을 뜻합니다. 눌려있던 버튼이 풀어진 상태라면(버튼이 튀어나와 있다면) 컴퓨터는 꺼진 상태이어야만 합니다.
<button type="button" aria-pressed="true"> Notify by email </button>
aria-pressed
와 같은 WAI-ARIA 상태 속성은 불리언 변수처럼 작동합니다. 그러나, checked
같은 표준 HTML 상태 속성과는 달리, true
혹은 false
둘 중 하나로 값을 명시해야 합니다. aria-pressed
속성만 추가한다고 해서 끝난 것이 아닙니다. 그리고, aria-pressed
속성이 존재하지 않는다면 안눌림(unpressed) 상태에 대해 알릴 수 있는 방법이 없는 것과 마찬가지 입니다. (aria-pressed
속성이 없는 버튼은 그냥 평범한 버튼일 뿐입니다.)
여러분의 각자의 상황에 맞추어 폼 안, 또는 밖에서 버튼을 사용하시면 됩니다. 하지만 만약 폼 내에서 사용해야 한다면, type="button"
부분이 중요해집니다. 만약 이 속성이 없다면, 몇몇 브라우저에서는 내부적으로 알아서 type="submit"
으로 속성을 부여한 후에 폼을 제출하려고 합니다. 그러나 폼 제출을 방지하려고 type="button"
을 사용하고, 여기에 다시 event.preventDefault()
까지 사용할 필요는 없습니다.
true
(켜짐)과 false
(꺼짐) 상태 사이를 왔다갔다 하려면 간단한 클릭 핸들러에게 이 일을 맡기면 됩니다. <button>
요소를 사용하고 있으므로, 요소를 마우스로 클릭하거나, 스페이스 혹은 엔터키로 누르거나, 아니면 터치 스크린에서 버튼을 탭하면 이벤트 타입이 촉발(trigger)되게 됩니다. <button>
요소에는 앞에 나열한 액션에 대한 각각의 대응 방식이 기본으로 내장되어 있습니다. 한번 HTMLButtonElement
인터페이스를 살펴 보면, disabled
와 같은 다른 속성들을 보게 될 텐데 이 속성들 역시 완벽하게 지원이 됩니다. <button>
요소를 사용하지 않고 있다면, 다음과 같은 스크립트를 사용해 버튼 동작을 흉내내야 합니다.
const toggle = document.querySelector('[aria-pressed]'); toggle.addEventListener('click', (e) => { let pressed = e.target.getAttribute('aria-pressed') === 'true'; e.target.setAttribute('aria-pressed', String(!pressed)); });
상태를 좀 더 명확하게
버튼 요소를 aria-pressed
속성과 함께 사용하면 몇몇 스크린 리더기에서 흥미로운 현상이 발생합니다. 바로 버튼이 “토글 버튼”이라고 인식이 되는 현상입니다. 혹은, 일부 경우 “푸시 버튼”이라고 인식되기도 합니다. 상태 속성이 들어가게 되면 버튼의 정체성이 바뀌게 됩니다.
NVDA를 사용해서 aria-pressed="true"
상태인 버튼에 초점을 주어보면, 스크린 리더기는 “이메일 알림 받기, 토글 버튼, 눌림”이라고 알려줍니다. “눌림” 상태라는 표현은 “체크됨”보다 더 적합한 표현입니다. 게다가, 폼 데이터를 입력하고 있다는 사용자의 의심도 피할 수 있습니다. 버튼이 눌리게 되면 그 즉시 “눌리지 않음”이라는 피드백이 돌아오게 됩니다.
스타일링
HTML 구조는 디자인 작업에 있어서, 그리고 웹에서 우리가 만든 모든 것들에서 중요한 역할을 합니다. 저는 HTML 우선 프로토타이핑™을 강하게 지지하는 사람입니다. 이를 통해 사이트의 스타일과 브랜드 작업을 하기 위한 기본 바탕이 잘 다져 졌는지 확실히 확인할 수 있기 때문입니다.
토글 버튼의 경우, 기본적으로 들어가야 하는 것은 다양한 입력(예: 음성 활성 소프트웨어)과 출력(예: 스크린 리더기)기기 모두 대응이 가능한 정도의 의미와 행동입니다. 이는 HTML을 사용하면 가능한 일입니다. CSS는 컨트롤을 시각적으로 인지할 수 있도록 만들기 위해 필요합니다.
형태는 기능을 따라가야만 하는데, CSS를 사용하면 손쉽게 달성할 수 있는 일입니다. HTML로 간단한 토글 버튼이 작동하도록 만들었고, 이 HTML 안에 들어있는 모든 요소를 전부 형태를 만들 때 사용해 볼 수 있습니다.
<button>
→button
요소 선택자aria-pressed="true"
→[aria-pressed="true"]
속성 선택자
일관성이 있고, 그러므로 이해하기 쉬운 인터페이스에서는 버튼들의 외관에 공통점이 있습니다. 버튼은 모두 버튼처럼 보여야만 합니다. 따라서 우리가 만들 기본 토글 버튼의 스타일은 아마도 button 요소 코드 블록에서 상속받아야 할 것 같네요.
/* 예를 들면... */ button { color: white; background-color: #000; border-radius: 0.5rem; padding: 1em 2em; }
“눌렸음” 상태를 시각적으로 나타낼 방법은 여러가지가 있습니다. 문자 그대로 해석해보면, 버튼이 눌렸다는 것을 보여주기 위해 inset box-shadow
를 사용해야 할 것 같습니다. 속성 선택자를 사용해 스타일링 해봅시다.
[aria-pressed="true"] { box-shadow: inset 0 0 0 0.15rem #000, inset 0.25em 0.25em 0 #fff; }
눌림/안눌림의 시각적인 비유를 마무리 지어보기 위해, 위치 관련 속성 몇개와 box-shadow
속성을 사용해 눌리지 않은 버튼이 “튀어 나와” 보이도록 만들겠습니다. 캐스케이딩 때문에 다음 코드 블록은 앞에 나온 [aria-pressed="true"]
코드 블록 보다 위에 와야 합니다.
[aria-pressed] { position: relative; top: -0.25rem; left: -0.25rem; box-shadow: 0.125em 0.125em 0 #fff, 0.25em 0.25em #000; }
(알아두기: 설명드린 방법은 그냥 하나의 아이디어로 생각해 주세요. “on”/”off” 레이블 등을 사용해 좀 더 버튼의 의미가 명확하도록 만드는 방법을 찾아본다면 사용자가 이해를 좀 더 쉽게 할 수 있을 것입니다.)
NOTE
색상에만 의존하지 마세요
“켜짐”은 주로 녹색으로 표시가 되고, “꺼짐”은 빨간색으로 표시가 됩니다. 이는 잘 성립된 하나의 규칙인데, 버튼에 사용한다고 해서 나쁠 것은 없습니다. 그러나, *오직* 색상만을 사용해 버튼의 상태를 표시하지 않도록 조심하세요. 만약 색상만에 의존한다면, 일부 색맹 사용자들은 두 상태의 차이를 인식하지 못하게 될 것입니다.
버튼을 이렇게 만들면 WCAG 2.0 1.4.1 Use Of Color (Level A)에 위배됩니다
초점 스타일
다른 모든 인터렉티브 컴포넌트처럼, 버튼 역시 초점 스타일을 가지고 있는 것이 중요합니다. 초점 스타일이 없다면 사람들이 키보드로 탐색을 할 때, 컨트롤 대상이 되는 요소들이 어떤 것들인지 알아볼 수가 없습니다.
최고의 초점 스타일은 레이아웃에 영향을 주지 않습니다. (초점이 요소들 사이를 움직일 때 인터페이스가 산만하게 흔들리면 안됩니다.) 일반적으로 사람들은 outline
을 사용해 스타일링 하는데, outline
은 대부분의 브라우저에서 직사각형만 그려냅니다. 버튼의 둥근 모서리에 맞추어 초점 스타일을 주고 싶다면, box-shadow
를 사용하는 편이 낫습니다. box-shadow
속성은 이미 사용하고 있으므로, 코드를 쓸 때 좀 더 신경을 써줘야 합니다. 눌린 상태이면서, 동시에 초점을 받고 있는 상태의 버튼에는 쉼표 두개로 구분된 box-shadow
속성값이 적용된 것을 아래 코드에서 확인해 보세요.
/* 기본 외곽선을 제거하고 outset 그림자 추가합니다 */ [aria-pressed]:focus { outline: none; box-shadow: 0 0 0 0.25rem yellow; } /* inset, outset 그림자 모두 들어 있어야 합니다 */ [aria-pressed="true"]:focus { box-shadow: 0 0 0 0.25rem yellow, inset 0 0 0 0.15rem #000, inset 0.25em 0.25em 0 #fff; }
레이블 교체하기
좀 전에 만들어본 토글 버튼에는 필요한 정보를 모두 들어간 고유 레이블이 디자인에 포함되어 있었고, 속성값에 따라 버튼 스타일이 확실히 바뀌도록 함으로써 꺼짐과 켜짐 상태 차이를 표시했습니다. 레이블이 “켜짐”과 “꺼짐”, 혹은 “재생”에서 “일시정지”로 변하는 버튼을 만들고 싶다면 어떻게 해야 할까요?
자바스크립트로 이 작업은 정말 쉽게 할 수 있는데, 조심해야 할 사항이 몇가지 있습니다.
- 만약 레이블이 바뀐다면, 상태는 어떻게 바꿔야 할까?
- 만약 레이블에는 “켜짐”과 “꺼짐”(“재생중”과 “일시정지”, 또는 “활성”과 “비활성”)만 표시되고 있다면, 버튼이 실제로 조정하는 대상은 어떻게 알 수 있을까?
이전의 토글 버튼 예제를 보면 레이블에 무엇이 켜지고 꺼지는지, 그 대상에 대한 언급이 있었습니다. 이 “무엇”의 대상이 일정치가 않다면, 점점 더 헷갈리기 시작합니다. 안눌린 상태에서는 “꺼짐”이였는데, 눌렸을 때는 “켜짐”이라고 바뀝니다. “꺼짐” 버튼으로 다시 되돌리려면 “켜짐” 버튼의 눌러진 상태를 풀어야 합니다. 네?
경험한 바에 따르면, 버튼의 눌림 상태와 레이블을 같이 바꾸면 절대 안됩니다. 버튼의 레이블이 바뀌고 있을 때는 이미 버튼의 상태는 WAI-ARIA 상태 관리자에 의해 단지 표면적으로만 바뀐 상태가 아니라, 실제 내부의 상태까지 바뀐 후입니다.
다음 예시 코드에서는 버튼의 레이블만 바꿉니다.
const button = document.querySelector('button'); button.addEventListener('click', (e) => { let text = e.target.textContent === 'Play' ? 'Pause' : 'Play'; e.target.textContent = text; });
이렇게 하면 문제점은 레이블 바뀐다고 해서 교체 여부가 바로바로 알림으로 나오지 않는다는 것입니다. 다시 말해, 재생 버튼을 눌러도 이에 상응하는 “눌림”이라는 피드백이 돌아오지 않습니다. 알림을 들으려면 버튼에 있는 초점을 풀었다가 다시 초점을 주는 식으로 수동으로 조작해야 바뀌었다는 것을 들을 수 있습니다. 비-시각장애인에게는 문제가 되지 않지만, 스크린리더기를 사용하는 시각 장애인들에게는 불친절한 방법입니다.
재생/일시정지 버튼은 주로 재생 아이콘(한쪽으로 기울어진 삼각형)과 일시정지 아이콘(수직선 두개) 사이를 왔다갔다 합니다. 화면에는 안 보이는 버튼 레이블은 항상 같은 문구로 유지하면서, 버튼의 상태만 바꿈으로써 내부 아이콘을 바꿔 볼 수도 있습니다.
<!-- 일시정지 상태 --> <button type="button" aria-pressed="false" aria-label="play"> ▶ </button> <-- 재생 상태 --> <button type="button" aria-pressed="true" aria-label="play"> ⏸ </button>
aria-label
속성값으로 들어가는 문구가 텍스트 노드에 들어있는 유니코드 심볼을 덮어쓰므로, 일시정지 상태의 버튼은 “재생 버튼, 눌리지 않음”, 재생 상태의 버튼은 “재생 버튼, 눌림”과 같은 알림이 나오게 됩니다.
꽤 괜찮은 방법이긴 한데, 음성 인식 및 활성화를 해야 하는 경우 문제가 됩니다. 음성 인식 소프트웨어를 사용한다면, 보통 직접 말로 레이블을 말해서 버튼을 구분해야 합니다. 만약 사용자가 일시정지 아이콘을 보게 된다면, 가장 처음 본능적으로 드는 생각은 “일시정지”이지, “재생”이 아닐 것입니다. 이런 이유 때문에 상태를 전환하는 것보다 레이블을 전환하는 일이 더 까다롭습니다.
레이블과 상태를 한번에 다 바꾸지 마세요. 한 번에 다 바꾸면, 위 예제의 경우 상태는 ‘눌린’ 상태의 ‘일시정지’인 버튼이 나오게 될 것입니다. 비디오 혹은 오디오가 이미 재생중인 상태라면, “일시정지(pause)” 상태가 버튼이 “켜졌다”는 뜻의 “눌림(pressed)”과 동일한 뜻이 될 수 없습니다.
보조 레이블 붙이기
때때로 실제로 “켜짐/꺼짐”이라고 읽히는 전원 스위치를 만들고 싶은 경우도 있습니다. 이런 버튼을 만들 때 핵심은 바로 각각의 토글 스위치와 그에 상응하는 보조 레이블의 관계가 깔끔하게 정리되었는지 확인하는 것입니다.
이메일 알림 설정이 비슷한 설정 옵션들과 하나의 그룹으로 묶여서 리스트 안에 나열된 경우를 가정해봅시다. 각각의 리스트 아이템은 설정에 대한 설명문 및 온오프 스위치로 구성되어 있습니다. 온오프 스위치에서는 디자인 요소로 “켜기”와 “끄기” 단어를 직접 사용합니다. <span>
요소를 몇개 추가하여 스타일링하는 용도로 써보겠습니다.
<h2>Notifications</h2> <ul> <li> Notify by email <button> <span>on</span> <span>off</span> </button> </li> <li> Notify by SMS <button> <span>on</span> <span>off</span> </button> </li> <li> Notify by fax <button> <span>on</span> <span>off</span> </button> </li> <li> Notify by smoke signal <button> <span>on</span> <span>off</span> </button> </li> </ul>
리스트의 덕목은 바로, 시각적으로도, 비시각적으로도 모두 아이템들이 그룹화 되어 있어서 서로 관련이 있음을 나타내줄 수 있어야 합니다. 이렇게 배치하면 사용자의 이해를 도울 뿐만 아니라, 몇몇 스크린 리더기기에서 탐색 지름길 용도로 리스트를 사용하기도 합니다. 예를 들어 JAWS에서는 L(리스트)와 I(리스트 아이템) 단축키로 리스트를 빠르게 탐색해 볼 수 있는 기능을 제공합니다.
각각의 ‘레이블’과 버튼은 공통의 리스트 아이템에 속하기 때문에 연관이 지어집니다. 그러나 명확하면서도 유일한 레이블을 부여해 놓지 않는다면 상황이 난처해 집니다. 음성 인식의 경우 특히 더 그렇습니다. aria-labelledby
를 써서 각각의 버튼과 리스트 텍스트 사이에 관계성을 부여해 볼 수 있습니다.
<h2>Notifications</h2> <ul> <li> <span id="notify-email">Notify by email</span> <button aria-labelledby="notify-email"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-sms">Notify by SMS</span> <button aria-labelledby="notify-sms"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-fax">Notify by fax</span> <button aria-labelledby="notify-fax"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-smoke">Notify by smoke signal</span> <button aria-labelledby="notify-smoke"> <span>on</span> <span>off</span> </button> </li> </ul>
aria-labelledby
값으로 각각의 적절한 span id
가 들어갔으므로 둘 사이의 관계가 생겼고, 버튼에 고유한 레이블을 붙일 수 있었습니다. aria-labelledby
속성은 필드의 id
가 어떤 것인지 알려주는 <label>
요소의 for
속성과 비슷합니다.
스위치 역할
중요한 사실은, ARIA 레이블을 사용하면 이 속성의 값이 각 버튼의 텍스트 컨텐츠를 덮어쓰게 되므로, aria-pressed
를 사용해 상태 전달을 할 수 있다는 것입니다. 그러나, 예제에서는 버튼들이 누가 봐도 “온오프” 스위치라고 할 수 있으므로, aria-pressed를 사용하는 방법 대신 WAI-ARIA 스위치 역할을 부여한 후 aria-checked
속성으로 상태 전달을 할 수도 있습니다.
<h2>Notifications</h2> <ul> <li> <span id="notify-email">Notify by email</span> <button role="switch" aria-checked="true" aria-labelledby="notify-email"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-sms">Notify by SMS</span> <button role="switch" aria-checked="true" aria-labelledby="notify-sms"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-fax">Notify by fax</span> <button role="switch" aria-checked="false" aria-labelledby="notify-fax"> <span>on</span> <span>off</span> </button> </li> <li> <span id="notify-smoke">Notify by smoke signal</span> <button role="switch" aria-checked="false" aria-labelledby="notify-smoke"> <span>on</span> <span>off</span> </button> </li> </ul>
활성화 상태를 어떻게 꾸밀지는 여러분의 선택에 달려있지만, 전 개인적으로 자바스크립트에서 <span>
클래스 속성을 사용하는 방식은 자제하는 편입니다. 그러는 대신, CSS 가상 클래스 선택자를 사용해 상태에 따라서 노출되는 span
을 선택할 수 있도록 코드를 작성해 보았습니다.
[role="switch"][aria-checked="true"] :first-child, [role="switch"][aria-checked="false"] :last-child { background: #000; color: #fff; }
설정을 왔다 갔다 하기
이제 이 설정 영역을 가로지르면서 탐색 할 수 있는 방법 두 가지에 대해 얘기해 보도록 하겠습니다. 하나는 탭키(초점을 받을 수 있는 요소 사이만 돌아다님)를 이용하는 방법이고, 다른 하나는 스크린 리더기(각 요소 사이를 돌아다님)로 탐색하는 방법입니다.
탭키로 탐색을 한다해도, 스크린 리더기는 인터렉티브 요소가 무엇이고 상태는 어떤지만 읽어주고 끝내지는 않습니다. 예를 들어, 첫번째 <button>
요소에 초점이 가게 되면, 그 버튼은 “이메일 알림 받기” 레이블이 붙은, “켜진” 상태의 스위치라는 말을 듣게 될 것입니다. “스위치”는 역할이며, 만약 이 역할이 요소에 부여된 상태라면 aria-checked="true"
는 “on”으로 읽어지게 됩니다.
NOTE
스위치 역할 지원상황
switch
역할은 aria-pressed
만큼 널리 지원되는 속성은 아닙니다. 예를 들어 Chrome의 확장 프로그램인 ChromeVox에서는 인식이 되지 않는 역할입니다.
하지만, ChromeVox에서 aria-checked
속성은 지원합니다. “스위치, 이메일 알림 받기, 켜짐“이라는 말이 들리는 대신에 “버튼, 이메일 알림 받기, 체크됨“이라는 말이 들리게 되는 것입니다. 이 말을 들어도 토글버튼이 그다지 떠오르지는 않지만, 틀린 말은 아닙니다. 꽤 자주 토글버튼이 아니라 체크박스 입력요소로 혼동 될 가능성이 있습니다.
신기하게도 NVDA를 사용하면 role="switch"
와 aria-checked="true"
속성이 있는 버튼이 눌림 상태에서는 토글 버튼으로 인식됩니다. ‘켜기/끄기’와 ‘눌림/안눌림’은 서로 등가가 맞기 때문에, 납득할 만합니다. (그러나 살짝 실망스럽기도 하네요.)
그러나 대부분의 스크린 리더기기에서는, 여러분이 지금 아이템 네개로 구성된 리스트에 들어오게 되었으며 현재 위치는 첫번째 아이템에 가 있다는 유용한 배경 정보도 듣게 될 것입니다. 이 글의 앞에서 다룬 그룹 레이블링과 약간 비슷한 역할을 하는 정보입니다.
알아두어야 할 것은, aria-labelledby
를 사용해 버튼 옆의 텍스트를 레이블과 연관지어 놓았기 때문에 스크린 리더기기로 탐색을 하더라도 여전히 그 관계가 영향을 끼친다는 것입니다.
아이템과 아이템 사이를 탐색할 때(예를 들어, NVDA 스크린 리더기기를 사용하면서 화살표 아래키를 사용하여), 초점이 가게 되는 곳은 전부 읽어줍니다. 제목(“알림, 제목 2“)을 포함해서 말입니다. 당연히 이런 상태로 탐색을 계속 하다보면 “이메일 알림 받기“라는 레이블이 혼자 먼저 읽히고 연관 버튼과도 묶여서 한번 더 읽히게 됩니다. 뭔가 중복이 발생한 것 같으나, 틀린 것은 아닙니다. “여기 설정 이름이 있어요. 그리고 이것은 설정과 관련된 온오프 스위치의 이름입니다.”
컨트롤 컴포넌트와 컴포넌트 조작 대상 사이의 관계를 얼마나 명백하게 규정지을지는 UX 디자인계에서 현재 계속 논의가 되고 있는 주제이며, 충분히 고려해 볼만한 사항입니다. 이 글에서는 일반 사용자들을 위해 클래식한 온오프 스위치 디자인을 그대로 놔두었습니다. 설정한 키보드 인터렉션 모드가 어떤 것이든지, 시각장애인, 혹은 일반인 사용자 중 스크린 리더기 사용자들이 헷갈리거나 잘못된 길로 들어서는 일이 생기지 않도록 하면서 말입니다. 꽤 힘든 과정이었습니다.
결론
토글 버튼을 어떻게 디자인하고 구현할지는 여러분께 달린 문제이긴하나, 나중에 여러분의 패턴 라이브러리에 특정 컴포넌트를 추가할 때가 오면 이 글이 머릿속에 떠오르기를 바랍니다. 토글 버튼(또는 이와 비슷한 어떤 인터페이스 컴포넌트든)이 일부 사용자들을 배제할 이유가 전혀 없습니다. 지금은 종종 일어나는 일이기는켜 하지만 말입니다.
여기서 살펴본 기본 지식들을 가져다가 쓰시고, 애니메이션을 포함한 모든 종류의 디자인적 요소들을 추가하시면 됩니다. 다만 먼저 탄탄한 기초 지식을 마련하는게 중요합니다.
체크리스트
- 사용자들에게 체크박스와 같은 폼 요소들이 폼 데이터 제출용이 아니라 온오프 토글용임을 인지시킬 수 있다면, 폼 요소들을 사용하세요.
aria-pressed
와aria-checked
는 링크가 아니라<button>
과 같이 사용하세요.- 레이블과 상태를 같이 바꾸지 마세요.
- “켜짐”, “꺼짐”과 같이 눈으로 볼 수 있는 텍스트 레이블(혹은 이와 비슷한 것)을 사용할 때는
aria-labelledby
를 사용해 텍스트를 덮어 쓸 수 있습니다. - 버튼의 텍스트와 배경색이 WCAG 2.0 요구사항에 적합한 수준의 대비를 가지고 있는지 확인하세요.
본 번역문을 참고한 제작기로 이어집니다.
0개의 댓글