앞 편에서 나는 아리아로 요소에 이름과 설명을 붙이는 법을 다뤘다. 이번 편은 그다음 단계인 역할과 상태다. 요소가 무엇인지를 알리는 것이 역할이고, 지금 어떤 형편인지를 알리는 것이 상태다. 펼쳐졌는지 접혔는지, 선택됐는지 아닌지 같은 정보가 상태에 담긴다. 이 둘을 제대로 다루면 복잡한 위젯도 낭독기 사용자에게 또렷이 전할 수 있다.
나는 예전에 접었다 펴는 메뉴를 만들면서 화면만 신경 썼다. 클릭하면 메뉴가 부드럽게 펼쳐졌고 눈으로는 완벽해 보였다. 그런데 낭독기로 들어보니, 메뉴가 지금 펼쳐졌는지 접혔는지 아무 안내가 없었다. 상태를 알리는 아리아를 빠뜨린 탓이었다. 그 경험 이후 나는 역할과 함께 상태를 챙기는 것을 습관으로 삼았다.
1. 역할이 요소의 정체를 알린다
역할은 요소가 무엇인지를 보조기기에 알리는 정보다. 이 요소는 버튼이다, 이 요소는 대화 상자다, 이 요소는 탭 목록이다 하는 정체를 role 속성으로 지정한다. 화면 낭독기는 이 역할을 읽어 사용자에게 이것이 무엇인지 알린다. 나는 역할을 요소의 신분증 같은 것으로 이해한다. 신분증이 있어야 낭독기가 요소를 제대로 소개한다.
다만 대부분의 역할은 네이티브 요소가 이미 갖고 있다. 버튼 요소는 버튼 역할을, 링크 요소는 링크 역할을, 목록 요소는 목록 역할을 태어날 때부터 지닌다. 그래서 이런 요소에는 역할을 따로 붙일 필요가 없다. 나는 역할을 지정하기 전에 먼저 이 정체를 가진 네이티브 요소가 없는지 찾는다. 있으면 그것을 쓰는 것이 훨씬 낫다.
역할을 직접 지정해야 할 때는 네이티브 요소로 표현할 수 없는 복잡한 구성일 때다. 탭 묶음이나 트리 구조처럼 대응하는 요소가 없는 위젯이 그렇다. 이럴 때 각 부분에 알맞은 역할을 붙여 낭독기가 구조를 이해하게 한다. 나는 이런 위젯을 만들 때 표준으로 정해진 역할 이름을 그대로 써서, 낭독기가 익숙하게 해석하게 한다.
역할을 붙일 때는 그 역할이 기대하는 조작도 함께 만들어야 한다. 탭 역할을 붙였다면 화살표로 탭 사이를 오갈 수 있어야 하고, 버튼 역할을 붙였다면 엔터로 눌려야 한다. 역할만 붙이고 조작을 빠뜨리면 낭독기 사용자의 기대를 배신하는 셈이다. 나는 역할과 조작을 짝으로 챙겨, 정체와 실제 동작이 어긋나지 않게 한다.
잘못된 역할은 없느니만 못하다. 요소에 엉뚱한 역할을 붙이면 낭독기가 잘못된 정보를 사용자에게 전한다. 목록이 아닌 것을 목록이라 알리거나, 버튼이 아닌 것을 버튼이라 알리면 사용자는 혼란에 빠진다. 나는 역할을 붙일 때 이 요소가 정말 그 정체인지 신중히 확인한다. 확신이 없으면 억지로 역할을 붙이기보다 구조를 다시 본다.
정리하면 역할은 요소의 정체를 알리는 신분증이지만 대부분 네이티브 요소가 이미 갖고 있으니, 직접 지정하는 것은 대응 요소가 없는 복잡한 위젯에 한하고 그 역할이 기대하는 조작도 함께 만든다. 나는 역할을 신중히 다룬다. 정체를 알렸다면, 이제 그 요소가 지금 어떤 형편인지를 알리는 상태로 넘어가자.
2. 접힘과 펼침을 알리는 상태
접었다 펴는 컨트롤에는 지금 펼쳐졌는지 접혔는지를 알리는 상태가 필요하다. 그 역할을 하는 속성이 aria-expanded다. 이 속성이 펼침을 뜻하는 값이면 낭독기가 펼쳐짐이라고, 접힘을 뜻하는 값이면 접힘이라고 알린다. 나는 아코디언이나 드롭다운 토글을 만들 때 이 속성을 꼭 붙여, 사용자가 컨트롤의 현재 상태를 알게 한다.
이 상태가 없으면 낭독기 사용자는 컨트롤을 눌러도 무슨 일이 일어났는지 알 수 없다. 눈으로 보는 사람은 메뉴가 펼쳐지는 걸 보지만, 낭독기 사용자에게는 아무 변화가 안 들린다. 눌렀는데 반응이 없는 것처럼 느껴지는 것이다. 나는 이 속성을 붙여, 컨트롤을 누를 때마다 상태가 바뀌었음을 소리로 전하게 한다. 상태가 곧 반응인 셈이다.
이 속성은 어떤 것을 여닫는지도 함께 알리면 좋다. 토글 버튼이 어느 영역을 제어하는지를 별도 속성으로 연결하면, 낭독기가 이 버튼이 무엇을 여닫는지까지 안내한다. 나는 토글 버튼과 그 대상 영역을 짝지어 연결해, 사용자가 버튼과 영역의 관계를 또렷이 알게 한다. 상태와 대상을 함께 알리는 것이 완성된 안내다.
중요한 것은 이 상태 값이 실제 화면과 늘 일치해야 한다는 점이다. 메뉴가 펼쳐졌으면 상태도 펼침이어야 하고, 접혔으면 상태도 접힘이어야 한다. 화면은 펼쳐졌는데 상태는 접힘으로 남으면 낭독기가 거짓을 말한다. 나는 이 값을 컨트롤을 누를 때마다 반드시 갱신해, 화면과 상태가 어긋나는 순간이 없게 한다.
이 상태를 스타일과 엮으면 관리가 편해진다. 상태 값에 따라 화살표 방향이 바뀌게 스타일을 걸어두면, 상태 하나만 갱신해도 화면과 안내가 함께 바뀐다. 나는 상태를 시각의 기준으로 삼아, 상태를 바꾸면 화면도 저절로 따라오게 짠다. 이렇게 하면 화면과 상태가 어긋날 여지가 줄어, 실수가 확 준다.
정리하면 접힘과 펼침을 알리는 이 상태는 여닫는 컨트롤에 꼭 필요하고, 대상 영역을 함께 연결하며, 무엇보다 실제 화면과 늘 일치하게 갱신해야 한다. 나는 이 상태를 시각의 기준으로 삼아 관리한다. 다음은 반대로 요소를 보조기기에서 감추는 상태다. 잘못 쓰면 위험한 속성이니 주의 깊게 보자.
3. 보조기기에서 감추는 상태
요소를 보조기기에서 감추는 속성이 aria-hidden이다. 이 속성을 켜면 요소가 화면에는 그대로 보이지만 접근성 트리에서는 사라져, 낭독기가 읽지 않는다. 눈에는 보이되 귀에는 안 들리게 하는 것이다. 나는 이 속성을 장식 요소나 중복된 아이콘처럼, 눈으로는 의미가 있지만 낭독으로는 군더더기인 요소에 쓴다.
대표적인 쓰임이 장식용 기호나 아이콘이다. 버튼에 이미 글자 이름이 있고 아이콘은 곁들인 장식이라면, 그 아이콘을 이 속성으로 감춰 중복 낭독을 막는다. 순수하게 분위기를 위한 기호도 마찬가지로 감춘다. 나는 낭독기로 들었을 때 의미 없이 읽히는 요소가 있으면, 이 속성으로 감춰 낭독의 흐름을 매끄럽게 다듬는다.
그런데 이 속성에는 치명적인 함정이 있다. 감추는 요소 안에 포커스를 받는 요소가 들어 있으면 안 된다는 것이다. 링크나 버튼을 이 속성으로 감추면, 낭독기에는 안 읽히는데 키보드로는 포커스가 가는 유령이 생긴다. 포커스는 갔는데 이름이 없어 사용자는 자기가 어디에 있는지 알 수 없다. 나는 이 함정을 특히 조심한다.
그래서 이 속성을 켜기 전에는 항상 그 안에 포커스 대상이 없는지 확인한다. 순수한 장식이나 이미 다른 곳에서 읽히는 중복 요소에만 쓰고, 조작할 수 있는 요소가 든 영역에는 쓰지 않는다. 만약 영역 전체를 감춰야 한다면, 그 안의 포커스 요소들도 함께 포커스를 못 받게 처리한다. 나는 감춤과 포커스를 늘 함께 챙긴다.
이 속성은 화면에서 완전히 숨긴 요소와는 다르다. 화면에서 감춘 요소는 낭독기에서도 자동으로 사라지지만, 이 속성은 화면에는 두고 낭독기에서만 감춘다. 둘의 쓰임이 다르다. 나는 눈과 귀 모두에서 숨길 때는 화면 감춤을, 눈에는 두고 귀에서만 숨길 때는 이 속성을 쓴다. 이 구분을 지켜야 의도대로 감출 수 있다.
정리하면 보조기기에서 감추는 이 속성은 장식이나 중복 요소를 낭독에서 빼는 데 쓰되, 포커스 받는 요소를 감싸면 이름 없는 유령을 만드니 절대 피하고, 화면 감춤과는 쓰임이 다르다. 나는 이 속성을 켤 때 포커스 대상을 먼저 확인한다. 다음은 현재 위치를 알리는 상태다.
4. 현재 위치를 알리는 상태
여러 항목 가운데 지금 어느 것이 현재인지를 알리는 속성이 aria-current다. 이 속성은 내비게이션에서 지금 보고 있는 페이지의 링크를 표시하는 데 자주 쓰인다. 값으로 현재 페이지를 뜻하는 것을 담으면, 낭독기가 이 링크가 현재 페이지라고 알린다. 나는 메뉴를 만들 때 현재 페이지 링크에 이 속성을 붙여 위치를 알린다.
눈으로 보는 사람은 현재 메뉴가 색이나 밑줄로 강조된 걸 보고 지금 위치를 안다. 그러나 낭독기 사용자에게는 그 시각 강조가 전해지지 않는다. 이 속성을 붙여야 낭독기가 이 링크가 현재라고 소리로 알린다. 나는 시각적 강조와 이 속성을 짝으로 붙여, 눈으로 보는 사람과 낭독기 사용자가 똑같이 현재 위치를 알게 한다.
이 속성은 페이지뿐 아니라 여러 맥락에서 쓸 수 있다. 여러 단계로 이루어진 절차에서 현재 단계를, 달력에서 오늘 날짜를 표시하는 식이다. 값을 상황에 맞게 골라, 지금 여기가 현재라는 것을 정확히 알린다. 나는 사용자가 어디에 있는지 헷갈릴 만한 자리마다 이 속성으로 현재를 짚어 준다. 위치를 알리는 것은 큰 배려다.
비슷하지만 다른 상태로 선택을 알리는 속성 aria-selected도 있다. 이 속성은 탭이나 옵션처럼 여러 개 중 하나를 고르는 자리에서 선택된 항목을 표시한다. 현재 위치를 알리는 속성과 쓰임이 겹칠 듯하지만, 하나는 지금 보고 있는 위치를, 하나는 사용자가 고른 선택을 알린다. 나는 상황에 맞는 속성을 골라 정확히 쓴다.
체크 상태를 알리는 속성 aria-checked는 또 다른 경우다. 체크박스나 토글처럼 켜짐과 꺼짐을 오가는 자리에 쓴다. 선택을 알리는 속성과는 맥락이 다르다. 나는 이 상태들을 헷갈리지 않으려고, 위치인지 선택인지 켜짐인지를 먼저 가린 뒤 알맞은 속성을 고른다. 각 상태에는 어울리는 자리가 정해져 있다.
정리하면 현재 위치를 알리는 이 속성은 내비게이션 현재 페이지 같은 자리에 쓰고, 선택을 알리는 속성이나 체크를 알리는 속성과는 맥락이 다르니 상황에 맞게 골라 쓴다. 나는 위치와 선택과 체크를 가려 정확한 속성을 쓴다. 이 모든 상태 속성에는 공통된 대원칙이 하나 있다. 마지막으로 그 원칙을 짚는다.
5. 상태는 반드시 동기화한다
역할과 상태 속성 전체를 관통하는 대원칙은 시각 상태와 아리아 상태를 항상 일치시키는 것이다. 화면에서 펼쳐졌으면 상태도 펼침이어야 하고, 선택됐으면 상태도 선택이어야 한다. 둘이 어긋나면 낭독기 사용자에게 거짓 정보가 전해진다. 나는 이 동기화를 상태 속성을 다루는 가장 중요한 규칙으로 여긴다.
가장 흔한 실수가 화면만 바꾸고 아리아 상태를 안 바꾸는 것이다. 클릭하면 스타일로 강조 표시만 옮기고, 아리아 상태 값은 옛날 그대로 두는 경우다. 그러면 눈에는 새 상태가 보이는데 낭독기에는 옛 상태로 남는다. 나는 이 어긋남을 막으려고, 시각을 바꿀 때 아리아 상태도 반드시 함께 바꾸는 것을 규칙으로 지킨다.
이 어긋남은 눈으로는 잘 드러나지 않아 놓치기 쉽다. 화면은 멀쩡하니 문제가 없어 보이지만, 낭독기로 들어보면 상태가 옛날 그대로인 것이다. 나는 상태를 바꾸는 코드를 짤 때마다 낭독기로 실제 낭독을 확인해, 상태 값이 화면과 함께 바뀌는지 귀로 점검한다. 눈으로만 보면 이 함정을 절대 알아챌 수 없다.
동기화를 쉽게 하려면 상태를 하나의 기준으로 삼는 것이 좋다. 아리아 상태 값을 진짜 상태로 정하고, 화면 스타일이 그 값을 따라오게 짜면, 상태 하나만 바꿔도 화면과 낭독이 함께 바뀐다. 나는 상태를 시각의 원천으로 삼아, 두 곳을 따로 관리하다 어긋나는 일을 없앤다. 하나의 기준이 있으면 어긋날 여지가 사라진다.
상태 동기화는 결국 낭독기 사용자에 대한 정직함의 문제다. 화면에 보이는 것과 낭독으로 들리는 것이 같아야, 낭독기 사용자가 화면을 신뢰하고 쓸 수 있다. 나는 상태를 다룰 때 이 정직함을 잊지 않으려 한다. 눈에 보이는 진실과 귀에 들리는 진실을 하나로 맞추는 것이 상태 속성을 쓰는 참뜻이다.
정리하면 역할과 상태의 대원칙은 시각 상태와 아리아 상태를 항상 일치시키는 동기화이며, 화면만 바꾸는 실수를 피하고, 상태를 하나의 기준으로 삼아 관리하며, 낭독기로 실제 낭독을 확인한다. 나는 이 동기화를 정직함의 문제로 대한다. 다음 편에서는 눈으로 보는 접근성의 기본인 색 대비와 가독성을 다룬다.