앞 편에서 포커스가 잘 흐르게 만드는 법을 다뤘다. 이제 그 포커스를 어떻게 보여주고 어디로 옮길지를 이야기할 차례다. 포커스는 키보드 사용자의 눈이다. 지금 어디에 있는지 보이지 않으면 사용자는 길을 잃고, 엉뚱한 곳에 놓이면 흐름이 끊긴다. 이번 편은 포커스를 또렷이 보이고 알맞은 곳으로 옮기는 포커스 관리를 다룬다.
나는 예전에 화면이 지저분해 보인다는 이유로 포커스 표시를 지워버린 적이 있다. 디자인은 깔끔해졌지만, 키보드로 써보니 포커스가 어디 있는지 도무지 알 수 없었다. 보이지 않는 커서를 더듬는 기분이었다. 그 뒤로 나는 포커스 표시를 함부로 지우지 않고, 오히려 더 또렷하게 다듬는 쪽으로 방향을 바꿨다. 이 편은 그 반성에서 출발한다.
1. 포커스 표시를 지우지 마라
포커스를 받은 요소에는 브라우저가 테두리 같은 표시를 그려준다. 이 표시가 키보드 사용자에게 지금 여기가 포커스라고 알려주는 유일한 신호다. 그런데 이 표시가 디자인상 거슬린다는 이유로 지우는 경우가 많다. 나는 이것이 접근성에서 가장 흔하면서도 뼈아픈 실수라고 본다. 표시를 지우는 순간 키보드 사용자는 눈을 잃는다.
포커스 표시를 없애는 대표적인 방법이 테두리 속성 outline을 outline: none으로 꺼버리는 것이다. 이렇게 하면 화면은 깔끔해지지만, 키보드로 이동할 때 포커스가 어디로 갔는지 전혀 보이지 않는다. 나는 이 코드를 볼 때마다 경고등이 켜진다. 표시를 끄려면 반드시 그것을 대신할 다른 표시를 마련해 두어야 한다.
포커스 표시를 없애면 안 된다는 것은 접근성 지침에도 분명히 있다. 포커스를 받은 요소는 어떤 형태로든 눈에 보여야 한다는 요구다. 색을 바꾸든, 테두리를 그리든, 그림자를 넣든 방식은 자유지만, 아무 표시도 없는 상태는 허용되지 않는다. 나는 이 요구를 지키기 위해 포커스 표시를 지우는 대신 디자인에 맞게 다시 그리는 길을 택한다.
기본 포커스 표시가 마음에 들지 않으면 없애는 게 아니라 바꾸는 것이 답이다. 브라우저가 그려주는 기본 테두리 대신 내 디자인에 어울리는 또렷한 표시를 직접 그리면 된다. 디자인과 접근성은 대립하지 않는다. 나는 포커스 표시를 브랜드 색으로 다듬어, 깔끔하면서도 눈에 잘 띄는 표시를 만들어 둔다. 둘 다 얻을 수 있는 문제다.
포커스 표시는 배경과의 대비도 충분해야 한다. 표시가 있어도 배경에 묻혀 잘 안 보이면 없는 것과 다름없다. 그래서 나는 포커스 표시를 만들 때 어떤 배경 위에서도 또렷이 드러나는지 확인한다. 얇고 흐릿한 표시보다 두껍고 대비가 큰 표시가 낫다. 포커스 표시는 은근한 것이 아니라 분명한 것이어야 제구실을 한다.
정리하면 포커스 표시는 키보드 사용자의 눈이므로 지워서는 안 되고, 거슬린다면 없애는 대신 디자인에 맞게 또렷하게 다시 그린다. 나는 표시를 끄는 코드를 볼 때마다 대안을 먼저 챙긴다. 그런데 마우스로 클릭할 때까지 표시가 나타나 지저분해 보이는 문제는 어떻게 풀까. 그 답이 다음 절의 조건부 포커스 표시다.
2. 키보드에만 표시를 보이는 법
포커스 표시를 지우고 싶은 마음의 뿌리에는 대개 마우스 문제가 있다. 마우스로 버튼을 클릭했을 뿐인데 포커스 표시가 남아 지저분해 보이는 것이다. 이 때문에 표시를 통째로 꺼버리면 키보드 사용자가 피해를 본다. 나는 이 딜레마를 오래 안고 있었는데, 조건부 포커스 표시를 알고 나서 말끔히 풀렸다.
그 해법이 :focus-visible라는 조건이다. 이 조건은 브라우저가 판단하기에 포커스 표시가 필요한 경우에만, 곧 키보드로 이동했을 때만 표시를 보여준다. 반대로 마우스로 클릭했을 때는 표시를 숨긴다. 나는 이 조건을 쓰면서 마우스 사용자에게는 깔끔한 화면을, 키보드 사용자에게는 또렷한 표시를 동시에 줄 수 있게 됐다.
예전에는 모든 포커스에 적용되는 :focus 조건만 있어서, 마우스든 키보드든 똑같이 표시가 떴다. 이 때문에 디자이너와 접근성 사이에 실랑이가 잦았다. 조건부 표시는 이 오랜 갈등을 해소한 반가운 기능이다. 나는 이제 포커스 표시를 이 조건에 걸어, 필요한 사람에게만 필요한 순간에 보이게 한다. 양쪽 요구가 자연스럽게 화해한다.
이 조건 덕분에 포커스 표시를 지울 이유가 거의 사라졌다. 마우스 클릭 때 표시가 거슬려서 꺼야 했던 것인데, 이제는 그럴 필요 없이 키보드에만 표시가 뜨기 때문이다. 나는 포커스 표시를 지우자는 요청을 받으면, 대신 이 조건부 표시를 제안한다. 지우는 대신 조건을 걸면 디자인과 접근성이 모두 만족한다는 걸 여러 번 확인했다.
조건부 표시를 쓰더라도 표시 자체는 여전히 또렷해야 한다. 조건은 언제 보일지를 정할 뿐, 얼마나 잘 보일지는 표시의 디자인이 정한다. 나는 이 조건에 걸리는 표시도 두껍고 대비가 큰 형태로 만들어, 키보드로 이동하는 순간 포커스가 어디 있는지 한눈에 들어오게 한다. 조건과 디자인을 함께 챙겨야 완성이다.
정리하면 조건부 포커스 표시는 키보드로 이동할 때만 표시를 보이고 마우스 클릭 때는 숨겨, 디자인과 접근성의 오랜 갈등을 해소한다. 나는 이 조건을 써서 포커스 표시를 지울 이유 자체를 없앤다. 그런데 표시가 잘 보여도, 그 포커스가 화면에서 가려지면 소용이 없다. 다음은 포커스가 가려지지 않게 하는 문제다.
3. 포커스가 가려지지 않게
포커스 표시가 또렷해도, 포커스를 받은 요소가 화면에서 가려지면 사용자는 여전히 그것을 볼 수 없다. 대표적인 경우가 화면 위에 고정된 머리글이다. 탭으로 이동한 요소가 이 고정된 머리글 뒤로 숨어버리면, 포커스는 갔는데 눈에는 안 보이는 상황이 생긴다. 나는 이 문제를 실제로 겪고 나서야 그 심각함을 알았다.
이 문제는 최신 접근성 지침에서 새로 다뤄지는 요구이기도 하다. 포커스를 받은 요소가 고정 요소나 배너 같은 것에 완전히 가려져서는 안 된다는 것이다. 적어도 일부는 보여야 사용자가 포커스 위치를 알 수 있다. 나는 화면에 고정 요소를 둘 때마다 그 뒤로 포커스가 숨지 않는지 반드시 점검하게 됐다.
흔한 해법은 포커스 대상 요소에 여유 공간을 두는 것이다. 스크롤로 요소가 화면에 들어올 때, 고정 머리글 높이만큼 위에 여백을 확보해 요소가 머리글 아래로 밀려나지 않게 한다. 이 여백을 주는 스타일 속성이 scroll-margin-top이다. 나는 고정 머리글이 있는 페이지에는 이 여백을 함께 설정해 포커스가 늘 드러나게 한다.
이 문제는 페이지 안의 링크로 이동할 때도 똑같이 나타난다. 목차의 링크를 눌러 특정 절로 점프했는데, 그 절의 제목이 고정 머리글에 가려 보이지 않는 경우다. 나는 이런 점프 대상에도 같은 여백을 두어, 이동한 지점이 머리글 아래로 숨지 않게 한다. 포커스든 점프든 사용자가 도착한 곳이 보여야 한다는 원칙은 같다.
포커스 가림은 눈으로 확인하기 전에는 알아채기 어렵다. 코드만 봐서는 문제가 없어 보이기 때문이다. 나는 고정 요소가 있는 페이지에서 탭으로 이동하며, 포커스가 화면 가장자리나 고정 요소 근처에 갔을 때 제대로 보이는지 직접 눈으로 확인한다. 특히 화면 위쪽과 아래쪽 경계에서 가림이 자주 생기니 그 부분을 유심히 본다.
정리하면 포커스는 표시가 또렷할 뿐 아니라 화면에서 가려지지 않아야 하며, 고정 머리글이 있는 페이지에서는 여유 공간을 두어 포커스 대상이 숨지 않게 한다. 나는 고정 요소가 있을 때마다 이 가림을 점검한다. 지금까지는 포커스를 보이는 문제였다면, 이제 포커스를 옮기는 문제로 넘어가자. 그 대표가 모달이다.
4. 모달의 포커스 관리
모달 창은 포커스 관리가 가장 중요한 곳이다. 대화 상자 역할 role="dialog"를 하는 이 창은 열리는 순간 사용자의 주의를 온전히 가져가야 한다. 그러려면 포커스를 관리하는 네 단계를 지켜야 한다. 열 때 안으로 옮기고, 안에 가두고, 이에스시로 닫고, 닫을 때 원래 자리로 되돌리는 것이다. 나는 이 네 단계를 모달의 기본 예절로 여긴다.
첫째, 모달이 열리면 포커스를 모달 안으로 옮긴다. 포커스가 여전히 뒤쪽 페이지에 남아 있으면, 키보드 사용자는 모달이 열린 줄도 모르고 보이지 않는 배경을 더듬게 된다. 나는 모달을 열 때 그 안의 첫 요소나 제목으로 포커스를 옮겨, 사용자가 곧바로 모달 안에서 시작하게 한다. 이것이 모달 포커스 관리의 출발이다.
둘째, 포커스를 모달 안에 가둔다. 모달이 열린 동안 탭으로 배경 요소에 닿으면 안 된다. 포커스가 모달의 마지막 요소를 지나면 다시 첫 요소로 돌아오게 순환시킨다. 이것은 앞 편에서 말한 의도적 포커스 가둠이다. 나는 이 가둠을 만들 때 반드시 이에스시 탈출구를 함께 두어, 가두되 언제든 빠져나올 수 있게 한다.
셋째, 이에스시 키로 닫을 수 있게 한다. 모달을 여는 것만큼 닫는 것도 키보드로 쉬워야 한다. 이에스시로 닫는 길이 없으면, 마우스로 닫기 버튼을 눌러야만 벗어날 수 있어 키보드 사용자가 곤란해진다. 나는 모달마다 이에스시 처리를 넣어, 어떤 상황에서도 키보드만으로 모달을 닫을 수 있게 보장한다.
넷째, 모달을 닫으면 포커스를 원래 자리로 되돌린다. 모달을 열었던 그 버튼으로 포커스를 되돌려야, 사용자가 원래 하던 흐름을 이어갈 수 있다. 포커스가 엉뚱한 곳이나 페이지 맨 위로 튀면 흐름이 끊긴다. 나는 모달을 열기 직전의 포커스 위치를 기억해 두었다가, 닫을 때 그 자리로 정확히 되돌리는 처리를 꼭 넣는다.
정리하면 모달은 열 때 포커스를 안으로 옮기고, 안에 가두되 이에스시 탈출구를 두고, 닫을 때 원래 자리로 되돌리는 네 단계로 관리한다. 나는 이 네 단계를 모달의 필수 예절로 삼는다. 모달만이 아니라 포커스를 옮겨야 할 상황은 또 있다. 마지막으로 동적 콘텐츠에서의 포커스 이동을 살펴보자.
5. 동적 변화와 포커스 이동
페이지가 바뀌지 않고 내용만 바뀌는 요즘의 웹에서는, 화면이 변할 때 포커스를 어디로 옮길지가 중요해진다. 새 내용이 나타났는데 포커스가 옛 자리에 그대로 있으면, 키보드 사용자는 변화가 일어난 줄도 모른다. 나는 화면이 크게 바뀔 때마다 포커스를 알맞은 곳으로 옮겨 사용자를 새 상황으로 안내한다.
대표적인 경우가 폼 제출 후 오류가 났을 때다. 오류가 여러 개면 오류 요약을 화면 위에 보여주고, 그곳으로 포커스를 옮긴다. 그래야 키보드 사용자가 무엇이 잘못됐는지 바로 알 수 있다. 나는 이 오류 요약을 프로그램적 포커스만 받는 자리로 만들어 두고, 제출이 실패하면 그곳으로 포커스를 점프시킨다. 사용자를 문제로 곧장 데려가는 것이다.
새 화면이 나타났을 때도 포커스를 옮긴다. 목록에서 상세로 넘어가거나 단계가 바뀔 때, 새로 나타난 영역의 제목으로 포커스를 옮기면 사용자가 지금 어디에 있는지 알 수 있다. 나는 큰 전환이 일어날 때 새 영역의 시작점으로 포커스를 옮겨, 키보드 사용자가 변화를 놓치지 않고 따라오게 한다. 포커스가 곧 안내인 셈이다.
다만 포커스를 함부로 옮기는 것도 문제다. 사용자가 무언가를 하고 있는 중에 예고 없이 포커스를 낚아채면 오히려 방해가 된다. 그래서 포커스 이동은 사용자의 행동에 대한 반응으로, 예측 가능한 순간에만 해야 한다. 나는 포커스를 옮길 때 이것이 사용자가 기대하는 이동인지를 먼저 묻고, 뜬금없는 이동은 피한다.
어떤 요소로 포커스를 옮기려면 그 요소가 포커스를 받을 수 있어야 한다. 오류 요약이나 영역 제목처럼 원래 포커스를 받지 못하는 요소에는, 프로그램적 포커스만 받는 표시를 붙여 두면 된다. 나는 포커스를 옮길 대상마다 이 처리를 미리 해두어, 필요할 때 정확히 그곳으로 포커스를 보낼 수 있게 준비한다.
정리하면 동적으로 화면이 바뀔 때는 오류 요약이나 새 영역의 시작점으로 포커스를 옮겨 사용자를 안내하되, 예측 가능한 순간에만 옮기고 대상 요소가 포커스를 받을 수 있게 준비한다. 나는 포커스를 안내인처럼 다룬다. 지금까지 시맨틱과 키보드, 포커스를 다뤘다면, 다음 편부터는 보조기기에 정보를 더하는 별도 속성의 세계로 들어간다.