요금제 비교표를 만든 적이 있다. 항목이 여덟 열, 요금제가 다섯 행. 데스크톱에서는 시원하게 펼쳐진 이 표가 자랑스러웠다. 그런데 휴대폰으로 열어보니 여덟 개 열이 손바닥만 한 화면에 우그러들어, 글자가 세로로 한 자씩 쪼개진 채 줄줄이 늘어서 있었다. 숫자는 서로 붙어 어느 요금제 것인지 분간이 안 갔다. 표는 반응형에서 가장 다루기 까다로운 물건이라는 걸, 나는 그 우그러진 화면을 보며 인정했다.
표가 어려운 이유는 그 본질이 이차원이기 때문이다. 가로로는 열, 세로로는 행. 이 격자는 넓은 화면을 전제로 태어났다. 그런데 모바일은 세로로 긴 화면이라 가로 공간이 늘 모자란다. 넓게 퍼지려는 표와 좁고 긴 화면이 정면으로 부딪치는 것이다. 이번 편은 이 충돌을 푸는 방법과, 표를 대신하거나 보완하는 카드라는 그릇을 함께 다룬다.
1. 표를 억지로 구겨 넣지 않기
가장 먼저 버려야 할 생각은 표를 무슨 수를 써서라도 화면 안에 다 넣겠다는 욕심이다. 열이 여덟 개인 표를 폭 삼백 몇십 픽셀에 다 담으려 하면, 각 칸은 사람이 읽을 수 없는 폭이 된다. 나는 그 사실을 인정하기까지 시간이 걸렸다. 만든 표를 온전히 보여주고 싶은 미련이 판단을 흐렸다.
표를 좁은 화면에 얹는 길은 크게 두 갈래다. 하나는 표의 격자 구조를 그대로 두고 가로로 밀어서 보게 하는 방법, 다른 하나는 격자를 풀어 행을 하나씩 세로로 쌓는 방법이다. 둘 다 나름의 값어치가 있고, 어느 쪽이 나은지는 표의 성격이 정한다. 나는 이 선택을 열의 개수와 데이터의 성격을 보고 가른다.
열이 적고 숫자를 서로 비교해야 하는 표라면 가로로 미는 편이 낫다. 열이 많고 각 행이 독립된 정보 뭉치라면 카드로 세우는 편이 낫다. 이 감을 잡기 전에는 모든 표에 같은 처방을 쓰려다 실패했다. 표마다 다른 옷을 입혀야 한다는 걸 여러 번 데고 나서야 알았다.
세 번째 길도 있긴 하다. 화면 폭이 아니라 표를 감싼 영역의 폭을 기준으로, 넓으면 격자를 유지하고 좁으면 카드로 바꾸는 혼합 방식이다. 같은 표를 사이드바 옆 좁은 자리에 놓든 본문 넓은 자리에 놓든, 그 자리 폭에 맞춰 알아서 형태를 고른다. 나는 같은 표 컴포넌트를 여러 위치에 재사용해야 할 때 이 방식을 쓰는데, 화면 폭만 보는 방식보다 손이 덜 간다. 다만 구조가 복잡해지니, 표가 한 군데에만 놓인다면 굳이 여기까지 갈 필요는 없다.
2. 가로로 미는 표
격자를 지키는 가장 단순한 방법은 표를 감싸는 상자를 두고 그 상자만 가로로 스크롤되게 하는 것이다. 표 자체는 원래 폭을 유지하되, 넘치는 부분은 상자 안에서 좌우로 밀어 본다. 페이지 전체가 흔들리는 게 아니라 표 한 칸만 미끄러지니, 앞 편에서 다룬 가로 스크롤 문제와도 어긋나지 않는다.
여기엔 놓치기 쉬운 접근성 함정이 있다. 이렇게 감싼 상자는 마우스로는 밀 수 있어도 키보드로는 밀 방법이 없는 경우가 많다. 나는 스크롤되는 상자에 초점을 받을 수 있게 하고, 이 영역이 무엇인지 알리는 이름과 역할을 붙여, 키보드로 화살표를 눌러 표를 밀 수 있게 한다. 마우스만 있는 세상을 가정하면 키보드 사용자가 표 오른쪽 절반을 영영 못 본다.
가로로 미는 표에서 내가 늘 챙기는 게 헤더 고정이다. 표를 오른쪽으로 밀다 보면 맨 왼쪽의 항목 이름 열이 화면 밖으로 사라져, 지금 보는 숫자가 무슨 항목인지 알 수 없어진다. 나는 첫 열이나 헤더 행을 제자리에 붙여두어, 아무리 밀어도 기준이 되는 이름표는 늘 보이게 한다. 이 하나가 있고 없고가 표를 읽을 만한 것과 읽을 수 없는 것으로 가른다.
사용자에게 밀 수 있다는 걸 알려주는 것도 잊지 않는다. 가로로 밀리는 표는 겉보기엔 그냥 잘린 표처럼 보여서, 옆으로 더 있다는 걸 모르고 지나치기 쉽다. 나는 오른쪽 끝에 살짝 그림자를 드리우거나 잘린 열의 일부를 일부러 걸치게 두어, 여기서 끝이 아니라는 신호를 준다. 스크롤이 되는 줄 모르면 그 스크롤은 없는 것과 같다.
스크롤 표에서 마지막으로 챙기는 건 손가락의 편안함이다. 좁은 화면에서 표를 미는 건 결국 엄지손가락 하나인데, 스크롤이 뻑뻑하거나 관성이 없으면 표를 미는 동작 자체가 성가시게 느껴진다. 나는 스크롤 상자가 손끝의 관성을 살려 부드럽게 미끄러지도록 하고, 스크롤 막대가 내용을 가리지 않게 다듬는다. 표를 밀어 본다는 건 사용자에게 추가 노동을 시키는 셈이니, 그 노동만큼은 최대한 매끄러워야 미안함이 덜하다.
3. 행을 카드로 세우기
열이 많은 표는 가로로 밀어도 답이 안 나온다. 밀어야 할 거리가 너무 길어 사용자가 지친다. 이럴 때 나는 표를 아예 해체해서, 각 행을 하나의 카드로 세로로 세운다. 한 사람의 정보가 가로 한 줄로 흩어져 있던 것을, 카드 한 장 안에 세로로 차곡차곡 담는 것이다.
이 변환의 핵심은 헤더를 각 칸 옆으로 데려오는 데 있다. 원래 표에서는 맨 위 헤더 행이 각 열의 뜻을 설명하는데, 행을 카드로 세우면 그 헤더가 저 위에 홀로 떨어져 각 값과의 연결이 끊긴다. 나는 각 칸에 그 칸이 무슨 항목인지를 데이터 속성으로 미리 심어두고, 좁은 화면에서 그 속성값을 칸 앞에 꺼내 보이게 한다. 값 왼쪽에 항목 이름, 오른쪽에 실제 값이 짝을 지어 나란히 앉는 구조다.
기법 자체는 화면에 안 보이던 헤더 텍스트를 각 칸 앞에 끌어다 붙이는 것이다. 좁은 화면에서 원래의 헤더 행은 시각적으로 감추고, 대신 각 칸이 자기 이름표를 앞에 달게 한다. 나는 이 방식으로 여덟 열짜리 요금제 표를, 요금제 하나하나가 한 장의 카드가 되는 세로 목록으로 바꿨다. 우그러들어 못 읽던 표가, 스크롤 몇 번이면 훑히는 깔끔한 목록이 됐다.
다만 이 변환에는 대가가 따른다. 표를 시각적으로 해체하는 과정에서 표가 원래 갖던 의미 구조가 망가지기 쉽다. 스크린리더는 표를 행과 열의 좌표로 읽는데, 배치를 카드로 바꾸느라 그 좌표 관계를 흐트러뜨리면, 눈으로 보는 사람에겐 멀쩡해도 소리로 듣는 사람에겐 뒤죽박죽이 된다. 나는 카드 변환을 할 때 시각적 배치만 바꾸고 표의 의미 구조 자체는 건드리지 않도록 조심한다. 보기 좋게 만들려다 못 듣게 만드는 건 반응형이 아니라 개악이다.
카드 변환의 또 다른 대가는 세로 길이다. 행 하나가 카드 한 장이 되면서 페이지가 세로로 훌쩍 길어진다. 행이 수십 개인 표라면 카드가 수십 장 쌓여 끝없이 스크롤해야 한다. 나는 데이터가 아주 많은 표에는 카드 변환보다 가로 스크롤을 택하거나, 아예 중요한 몇 열만 추려 보여주고 나머지는 접어두는 쪽을 고민한다. 모든 데이터를 다 보여주는 게 늘 친절인 것은 아니다.
4. 카드라는 그릇
표 이야기를 하다 보면 자연스럽게 카드로 넘어온다. 카드는 하나의 정보 뭉치를 네모난 그릇에 담아 독립적으로 보여주는 단위다. 반응형에서 카드가 사랑받는 이유는, 카드가 화면 폭에 따라 개수와 배치를 유연하게 바꾸기 좋은 그릇이기 때문이다.
카드를 여러 장 늘어놓을 때 나는 격자를 쓴다. 예전에는 화면 폭마다 몇 열로 놓을지를 미디어 쿼리로 일일이 지정했다. 좁으면 한 열, 중간이면 두 열, 넓으면 세 열, 이런 식이었다. 이건 브레이크포인트를 관리하는 수고가 만만치 않았다. 지금은 카드 한 장의 최소 폭만 정해주고, 화면이 허락하는 만큼 알아서 열 개수를 채우게 둔다. 화면이 넓어지면 열이 저절로 늘고, 좁아지면 한 열로 접힌다. 브레이크포인트 없이도 카드가 스스로 흐른다.
이 방식의 묘미는 특정 기기 크기를 아예 신경 쓰지 않아도 된다는 데 있다. 카드가 몇 픽셀 아래로는 안 좁아지겠다는 하한만 알려주면, 나머지는 화면과 카드가 알아서 협상한다. 나는 이걸 알고 나서 카드 그리드에 미디어 쿼리를 거의 쓰지 않게 됐다. 한 줄로 반응형이 완성되는 흔치 않은 경우다.
물론 이 자동 배치에도 조심할 구석은 있다. 카드가 딱 한 장만 남는 마지막 줄에서, 그 한 장이 화면 전체 폭으로 쭉 늘어나 어색해지는 경우가 있다. 세 장씩 놓이다가 마지막에 홀로 남은 카드가 세 배 폭이 되면 균형이 깨진다. 나는 이럴 때 카드가 늘어날 수 있는 최대 폭에 상한을 두거나, 마지막 줄만 왼쪽으로 정렬해 자연스럽게 흘려보낸다. 자동에 맡기되 그 자동이 만들어내는 극단만 슬쩍 다듬는 것이다.
카드 하나의 속을 짜는 것도 반응형의 일부다. 카드 안에는 보통 이미지, 제목, 설명, 버튼이 세로로 쌓인다. 나는 카드가 좁을 때와 넓을 때 이 요소들의 배치가 달라지길 원하는데, 여기서는 화면 폭이 아니라 카드 자신의 폭을 봐야 한다. 같은 카드가 한 열일 때는 넓고 세 열일 때는 좁으니, 화면 폭만 보면 어긋난다. 카드 안쪽은 카드의 폭에 반응하게 짜야 어디에 놓이든 제 모습을 지킨다. 이건 그 자체로 큰 주제라 여기서는 씨앗만 남겨둔다.
카드를 다룰 때 내가 실수로 배운 게 높이 맞춤이다. 한 줄에 놓인 카드들의 안쪽 내용 길이가 제각각이면, 어떤 카드는 키가 크고 어떤 카드는 작아 줄이 들쭉날쭉해진다. 예전엔 이걸 억지로 같은 픽셀 높이로 고정했다가, 내용이 넘치는 카드에서 글자가 잘리는 사고를 냈다. 지금은 격자와 유연 상자가 제공하는 늘림 정렬에 맡겨, 같은 줄의 카드들이 가장 키 큰 카드에 맞춰 함께 늘어나게 둔다. 고정 높이는 내용이 그 안에 영원히 딱 맞을 거라는 헛된 믿음이다.
카드에 이미지를 얹을 때도 함정이 있다. 카드마다 이미지 원본 비율이 다르면 어떤 카드는 이미지가 길쭉하고 어떤 카드는 납작해, 줄이 어수선해진다. 나는 카드 이미지 영역에 일정한 비율을 지정하고, 원본이 어떻든 그 틀에 맞춰 채워지게 한다. 이러면 이미지 비율이 제각각인 자료를 받아도 카드 줄이 가지런히 정렬된다. 이 하나로 카드 그리드의 인상이 확 정돈된다.
5. 무엇을 언제 쓸지
정답은 표마다 다르다. 나는 이 선택을 몇 가지 질문으로 가른다. 데이터를 서로 비교해야 하나, 아니면 각 행을 따로 읽나. 열이 몇 개나 되나. 행이 수십 개인가 몇 개인가. 이 답에 따라 가로 스크롤, 카드 변환, 아니면 아예 다른 형태 중 하나가 정해진다.
비교가 핵심인 표는 격자를 지켜야 한다. 요금제 A의 저장 용량과 요금제 B의 저장 용량을 같은 눈높이에서 나란히 봐야 비교가 되는데, 카드로 세워 서로 다른 화면에 흩어놓으면 그 비교가 불가능해진다. 이런 표는 열을 몇 개만 추려서라도 격자를 유지하는 게 낫다. 반대로 연락처 목록처럼 각 행이 완결된 한 사람인 표는 카드로 세우는 게 자연스럽다.
열을 추리는 결정도 생각보다 중요하다. 데스크톱에서 여덟 열을 다 보여줬다고 모바일에서도 여덟 개가 다 필요한 건 아니다. 나는 좁은 화면에서는 사용자가 가장 먼저 궁금해할 두세 열만 남기고 나머지는 상세로 미룬다. 요금제 표라면 이름과 가격, 딱 그 둘이면 첫 선택은 충분하고, 세부 항목은 눌러 들어가서 보게 하면 된다. 모든 열을 다 이고 지려다가 아무 열도 제대로 못 읽히게 만드는 것보다, 핵심 몇 개를 또렷이 보여주는 편이 사용자에게 이롭다.
내가 오래 걸려 배운 건, 표를 표로만 생각하지 않는 유연함이었다. 어떤 데이터는 애초에 표가 아니라 목록이나 카드가 더 어울렸는데, 표라는 형식에 갇혀 억지로 격자에 밀어 넣고 있었다. 데이터의 성격을 먼저 보고 그릇을 고르면, 반응형으로 애먹을 일 자체가 줄어든다. 나는 이제 새 데이터를 받으면 이걸 꼭 표로 담아야 하나부터 되묻는다. 형식은 데이터를 담는 그릇일 뿐, 데이터를 위해 사람이 형식에 맞춰 고생할 이유는 없다. 다음 편에서는 화면 폭에 따라 통째로 모습을 바꾸는 또 하나의 까다로운 물건, 내비게이션을 다룬다.