담당 서비스에서 신규 추가되는 UI를 제작하다가, 토글 스위치가 등장하는 부분이 있어서 이를 시험삼아 컴포넌트화 해보기로 마음 먹었습니다.
만들기 전에 우선 포용적 컴포넌트: 토글 버튼편(Inclusive Components – Toggle Button) 번역글을 읽어보니 도움이 많이 되었습니다. 마지막 부분에 핵심이 다 적혀 있습니다. 토글 버튼을 만들 때 체크해야 하는 사항들은 다음과 같습니다.
- 사용자들에게 체크박스와 같은 폼 요소들이 폼 데이터 제출용이 아니라 켜기/끄기 토글용임을 인지시킬 수 있다면, 폼 요소들을 사용해도 됩니다.
aria-pressed
와aria-checked
는 링크가 아니라<button>
과 함께 사용하세요.- 레이블과 상태를 같이 바꾸지 마세요.
- “켜짐”, “꺼짐”과 같이 눈으로 볼 수 있는 텍스트 레이블(혹은 이와 비슷한 것)을 사용할 때는
aria-labelledby
를 사용해 텍스트를 덮어쓰기를 할 수 있습니다. - 버튼의 텍스트와 배경색이 WCAG 2.0 요구사항에 적합한 수준의 대비를 가지고 있는지 확인하세요.
HTML
.toggle_button(disable-all='$ctrl.dimmed') button.btn_switch(role='switch', type='button', aria-checked='{{$ctrl.checkedStatus}}', aria-label='{{$ctrl.buttonLabel}}', ng-click='$ctrl.changeStatus()') span.btn_switch_status(ng-show='!$ctrl.checkedStatus') OFF span.btn_switch_status(ng-show='$ctrl.checkedStatus') ON span.btn_switch_label {{$ctrl.buttonLabel}}
.toggle_button
이라고 컴포넌트 전체 클래스를 일단 잡아준 뒤 안에 버튼 마크업을 합니다. 컴포넌트별로 파일명과 동일하게 최상단 클래스를 잡아주어야 나중에 스타일 먹일 때도 편하고, 열어놓은 파일이 어떤 것인지 빨리 파악하기에도 편하더라구요. (토글 스위치의 경우, 파일명이 toggle-button.pug
이므로 최상단 클래스 역시 toggle-button
으로 통일)별게 없습니다. <button>
요소에 role='switch'
을 붙여주고, aria-checked
로 on/off 상태 제어, aria-label
로 버튼 이름을 붙여줍니다.
aria-checked
속성은 컨트롤러의 checkedStatus
값이 들어가게 됩니다. 버튼이 눌렸으면 true
, 버튼이 눌리지 않았으면 false
가 됩니다. 아이맥 보이스오버에서 테스트를 해 보았는데, aria-pressed
속성을 사용하면 속성값이 바뀌어도 바로 알람이 오지 않는 반면 aria-checked
속성은 누를 때마다 상태 변화를 알려줍니다.
레이블과 상태를 같이 바꾸지 마라는 위의 체크리스트 항목을 참조하여서, aria-label
의 값은 처음 버튼이 마크업에서 정의될 때 넣는 문자열 값으로 동일하게 유지시키겠습니다. $ctrl.buttonLabel
값이 aria-label
속성에서도 사용이 되고, 혹시 모르니 span
요소안의 텍스트로도 넣어주도록 하겠습니다.
그리고 원래 디자인에는 없었으나, 제가 추가해 본 것은 ON/OFF 상태 텍스트 입니다. 번역글을 참조해보시면, ‘색상에만 의존하는 버튼 상태 표시 방식‘은 색맹과 같은 일부 사용자들을 배제시키는 디자인이 되버리므로, 한번 텍스트를 추가해보도록 하겠습니다.
CSS
@charset "utf-8"; .toggle_button { .btn_switch { position: relative; width: 55px; height: 24px; padding: 2px; box-sizing: border-box; border-radius: 21px; vertical-align: top; transition: background-color 0.1s ease; &::before { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 10px; background-color: #fff; transition: transform 0.1s ease; } } .btn_switch_label { margin-left: 5px; vertical-align: -2px; } .btn_switch_status { position: absolute; top: 2px; color: #fff; } .btn_switch[aria-checked='false'] { background-color: #dbdbdb; &::before { transform: translateX(0); } .btn_switch_status { right: 7px; } } .btn_switch[aria-checked='true'] { background-color: $color-palettes-info; &::before { transform: translateX(31px); } .btn_switch_status { left: 12px; } } }
버튼은 이미지로 넣기 귀찮으니 CSS로 그리도록 하겠습니다. 적당히 눈대중으로 픽셀을 맞추어 가면서 쓱쓱 그려보도록 합시다. 스타일에서도 aria-checked
속성을 선택자로 써서 버튼의 행동에 따라 스타일이 바뀌도록 만들었습니다.
버튼의 상태 텍스트(버튼 내에 나타나는 시각적인 텍스트)에는 .btn_switch_status
라는 공통 클래스만 주어서, 이 클래스 선택자로 ON/OFF 선택자의 위치값을 조정했습니다. aria-checked
속성에 의해 ON/OFF는 표시가 제어되므로 따로 .is_on
, .is_off
와 같은 상태 표시용 클래스를 주지 않았습니다.
JavaScript
import template from './toggle-button.pug' class toggleButtonController { constructor($scope) { this.$scope = $scope this.checkedStatus = false } changeStatus() { this.checkedStatus = !this.checkedStatus } } export default angular.module('cuminApp.toggle-button', []) .component('toggleButton', { template, controller: toggleButtonController, bindings: { buttonLabel: '=', checkedStatus: '=', dimmed: '=', }, }) .name
버튼이 눌릴때마다 상태 변화가 감지 가능하도록 컨트롤러에 changeStatus()
메소드를 구현해 놓고, checkedStatus
변수 역시 초기값 false
로 놓도록 하겠습니다. 혹시 여기서 수정할 것이 있다면 저 좀 알려주세요~ 다른 분들이 작성해 놓은 코드를 뼈대만 뽑아서 가져다 놓고 버튼 컴포넌트를 구현할 때 필요한 것만 최소한으로 넣었습니다.
만약 마크업에서 <button>
을 쓰지 않고 <span>
이라던가 <div>
로 버튼 형태만 만들어 놓았다면, 엔터키나 스페이스바로 버튼 선택을 하지 못하게 되므로 따로 코드 구현이 필요합니다. 귀찮으므로 되도록이면 <button>
을 쓰는 것이 좋습니다.
토글 버튼은 필요한 곳에서 다음과 같은 코드로 사용하면 됩니다. button-label
속성값으로는 버튼 레이블을 문자열로 넣어줍니다.
toggle-button.is_inline_block(button-label='"예약 노출"')
정리
지금 생각해보니, 마크업 구조를 toggle-button
컴포넌트 안에는 <button>
만 놓고, 레이블 역할을 하는 <span>
요소는 밖으로 빼도 괜찮을 것 같네요. 그런 후에, aria-labelledby
로 <span>
과 toggle-button
내의 <button>
사이의 연관을 만들어 주어도 되는데, 저는 그냥 한번에 묶어서 보고 싶어서 저런식으로 작성했습니다. (뭔가 잘 뭉쳐놓고 싶은 마음에…)
번역해놓은 글을 처음 볼 때는 ‘토글 버튼 하나 구현하는 데 뭐 이리 생각해 볼게 많아?!’라고 생각했는데, 지금보니 읽기 잘했다는 생각이 듭니다. 간단히 보여도 버튼의 상태와 레이블이 따로 놀게 되면 나는 그저 혼란스러운 컴포넌트를 구현한 것에 불과한게 되어 버리니깐요. 시간들어서 만들었는데, 혼란만 가중시킨다면 슬플것 같습니다.
2개의 댓글
포용적 컴포넌트: 토글 버튼 | WIT - NTS UIT Blog · 2017년 12월 11일 10:14 오전
[…] 번역문을 참고한 적용기로 […]
포용적 컴포넌트: 토글 버튼 | WIT블로그 · 2019년 11월 25일 3:33 오후
[…] 번역문을 참고한 제작기로 […]