
한 줄 요약: 가로로 넘기는 카드 목록을 두 줄로 만들었더니 1위 옆에 4위가 붙어 읽는 순서가 뒤엉켰습니다. 브라우저가 칸을 세로로 먼저 채우기 때문이었고, 화면에 그리기 전에 6개씩 묶어 순서를 다시 짜서 고쳤습니다. 이 글은 그 순서표와 코드, 그리고 순서를 바꿀 때 놓치기 쉬운 접근성 문제까지 정리합니다.
증상: 두 줄로 놓았더니 순위 순서가 뒤엉켰다
블로그 목록형 화면에서 카드를 두 줄로 늘어놓았더니 읽는 순서가 뒤엉켰습니다. 1위 다음에 2위가 와야 하는데 4위가 옆에 붙어 있었습니다. 순위가 중요한 목록이라서 이 어긋남이 특히 눈에 띄었습니다. 번호가 있는 목록에는 왼쪽에서 오른쪽으로, 윗줄에서 아랫줄로 읽는다는 암묵적인 약속이 있고, 이 약속이 깨지면 독자는 카드 내용을 읽기도 전에 목록이 잘못됐다고 느낍니다. 영화 목록처럼 번호가 곧 정보인 카드에서는 순서가 틀리면 목록 전체가 신뢰를 잃습니다.
이 글은 같은 문제를 겪는 분을 위해 원인, 해결 코드, 적용할 때 바꿔야 하는 숫자를 차례로 풀어 씁니다. 코드는 자바스크립트로 데이터 배열의 순서를 바꾸는 방식이며, 스킨의 HTML 구조를 따로 건드리지 않아도 됩니다. 마지막에는 CSS로 푸는 방법과의 비교, 그리고 이 방법이 가진 한계도 적었습니다.
읽고 나서 가져가실 것은 세 가지입니다. 원인을 한 줄로 설명하는 말, 그대로 옮겨 쓸 수 있는 함수, 그리고 내 화면에 맞추려면 어느 숫자를 바꿔야 하는지입니다. 어떤 화면에 이 방법이 맞고 어떤 화면에는 맞지 않는지의 판단 기준도 함께 적었습니다.
원인: 가로 스크롤에서 두 줄을 만들면 칸은 세로로 먼저 찬다
문제의 원인은 칸을 채우는 방향입니다. 가로로 넘기는 목록을 두 줄로 만들면, 브라우저는 위 칸을 다 채우고 아래로 내려가는 것이 아니라 세로로 먼저 채웁니다. 그래서 1위 아래에 2위가 오고, 그 옆에 3위가 옵니다. 보는 사람은 1위 옆에 2위가 있기를 기대합니다.
MDN 문서는 CSS 그리드의 grid-auto-flow가 항목을 어느 방향으로 채울지 정한다고 설명합니다. 값이 row이면 행을 하나씩 채우며 필요할 때 행을 더하고, column이면 열을 하나씩 채우며 필요할 때 열을 더합니다. 기본값은 row입니다. 가로로 계속 이어지는 두 줄 목록은 열을 더해 가며 채우는 구조여서 항목이 위아래로 먼저 쌓이는 것으로 볼 수 있습니다. 이 설명대로라면 문제는 코드의 오류가 아니라 채우는 방식의 성질입니다. MDN은 dense 값도 설명하는데, 앞쪽의 빈자리를 메우려고 뒤의 작은 항목을 끌어올려 항목이 소스 순서와 다르게 보일 수 있다고 합니다. 순위 목록에서는 이 값을 쓰지 않는 편이 안전하다는 뜻이 됩니다.
보이는 순서에 맞게 데이터를 미리 섞는다
CSS로 순서를 바꾸는 방법도 있지만, 데이터 순서를 아예 다시 짜는 쪽을 택했습니다. 화면에 그리기 전에 배열을 재배치합니다. 아래가 그 함수입니다.
function orderMoviesForTwoRows(items){
items = items || [];
if(isGpMovieMobileLayout()) return items; // 폰은 한 줄이라 그대로
var ordered = [];
var pageSize = 6; // 한 화면에 3열 x 2행
for(var start = 0; start < items.length; start += pageSize){
var page = items.slice(start, start + pageSize);
for(var col = 0; col < 3; col++){
if(page[col]) ordered.push(page[col]); // 윗줄
if(page[col + 3]) ordered.push(page[col + 3]); // 아랫줄
}
}
return ordered;
}
6개씩 끊는 이유는 한 화면에 3열 2행이 보이기 때문입니다. 그 6개 안에서 1·4위, 2·5위, 3·6위 순으로 다시 세웁니다. 이러면 브라우저가 세로로 채워도 결과적으로 가로 순서가 맞습니다.

6개 묶음의 순서표: 어떤 순위가 어디에 놓이나
코드가 하는 일을 표로 풀면 이해가 쉽습니다. 아래 표는 위 함수에 한 묶음 6개를 넣었을 때의 결과입니다. 배열을 새로 만든 순서와, 브라우저가 열마다 위에서 아래로 채운 뒤 화면에 보이는 위치를 나란히 적었습니다. 이 표는 함수의 코드와 앞서 설명한 채우기 방향을 따라 계산한 것입니다.
| 새 배열의 순서 | 원래 순위 | 화면 위치(열, 줄) |
|---|---|---|
| 1번째 | 1위 | 1열, 윗줄 |
| 2번째 | 4위 | 1열, 아랫줄 |
| 3번째 | 2위 | 2열, 윗줄 |
| 4번째 | 5위 | 2열, 아랫줄 |
| 5번째 | 3위 | 3열, 윗줄 |
| 6번째 | 6위 | 3열, 아랫줄 |
윗줄을 왼쪽에서 오른쪽으로 읽으면 1, 2, 3위이고, 아랫줄은 4, 5, 6위입니다. 눈으로 읽는 순서와 순위가 일치합니다. 표를 읽는 요령은 간단합니다. 새 배열에서 홀수 번째는 윗줄, 짝수 번째는 아랫줄에 놓이고, 7번째 항목부터는 다음 묶음이 시작됩니다. 다만 새 배열의 순서는 1, 4, 2, 5, 3, 6이라는 점에 주의하세요. 이 차이가 뒤에서 다룰 접근성 문제의 원인입니다.
예시입니다. 이 표는 3열 2행일 때만 맞습니다. 만약 4열 2행 화면이라면 묶음 크기는 8개가 되고, 윗줄은 1~4위, 아랫줄은 5~8위가 되도록 반복 횟수와 더하는 숫자를 4로 바꿔야 합니다.
개수가 6의 배수가 아닐 때
영화 수가 6의 배수로 딱 떨어지지는 않습니다. 그래서 if(page[col])로 있는 것만 넣고, 없는 자리는 그냥 건너뜁니다. 처음에는 빈 칸을 채우려고 가짜 항목을 넣었는데, 회색 빈 카드가 보이는 쪽이 더 이상했습니다. 그냥 짧게 끝나는 것이 자연스러웠습니다. 있는 것만 넣는 검사를 빼면 비어 있는 자리를 처리하다 오류가 날 수 있으니, 이 한 줄은 지우지 않는 편이 좋습니다.
가상 예시입니다. 항목이 8개라면 첫 묶음 6개는 위 표대로 놓이고, 둘째 묶음에는 7위와 8위만 남습니다. 코드를 따라 계산하면 둘째 묶음은 7, 8 순으로 배열에 들어가고, 열마다 위에서 아래로 채우는 방식에서는 이 둘이 같은 열의 위아래에 놓일 수 있습니다. 마지막 묶음은 화면에서 한 번 눈으로 확인해 보시기 바랍니다.
확인 요령도 적어 둡니다. 개발자 도구에서 항목 수를 6개, 7개, 8개, 9개, 12개로 바꿔 가며 화면을 보면 됩니다. 6의 배수일 때는 위 표와 정확히 맞는지, 아닐 때는 마지막 열이 어떻게 놓이는지를 볼 수 있습니다. 어긋남이 눈에 띄면 마지막 묶음만 따로 처리하는 분기를 넣을지 판단하시면 됩니다.

폰은 그대로 둔다
함수 맨 앞줄에서 폰이면 원래 순서를 그대로 돌려줍니다. 폰에서는 한 줄로만 보여주기 때문에 섞을 필요가 없습니다. 오히려 섞으면 순서가 망가집니다. 이 조건 하나가 빠지면 폰에서는 1, 4, 2, 5 순으로 한 줄에 나열되는 문제가 생깁니다. 한 줄로 보이는 화면에서는 소스 순서가 곧 화면 순서이기 때문입니다.
화면 구조에 따라 데이터 순서가 달라지는 코드라서, 둘을 같이 봐야 이해가 됩니다. 나중에 열 개수를 바꾸면 여기 숫자도 같이 고쳐야 합니다. 폰인지 판단하는 기준은 화면 스타일 쪽의 기준과 같아야 하므로, 한쪽만 바꾸지 않도록 같이 확인하세요. 적용하실 때는 pageSize와 반복문의 3, 그리고 page[col + 3]의 3을 자기 화면의 열 수와 맞추면 됩니다. 이 세 곳이 서로 묶여 있다는 점이 이 방법의 약한 고리입니다.
- pageSize를 한 화면에 보이는 칸 수(열 수 x 줄 수)로 맞춥니다.
- 바깥 반복문의 횟수와 page[col + 3]의 3을 열 수에 맞춥니다.
- 폰 여부를 판단하는 함수가 실제 화면 폭 기준과 같은지 봅니다.
순서를 바꿀 때 놓치기 쉬운 접근성
MDN의 그리드와 접근성 문서는 그리드로 항목을 다시 배치하는 일은 시각적인 재배열일 뿐이라고 설명합니다. 화면 낭독이나 Tab 키 이동 순서는 HTML 소스 순서를 따르고, 그리드 배치는 비시각 매체의 순서를 바꾸지 않습니다. 문서는 순서 관련 속성을 시각적 재배열에만 쓰라고 안내하며, 소스 순서를 대신할 수 없다고 강조합니다.
W3C의 CSS 그리드 명세도 같은 취지로 적고 있습니다. 작성자는 order와 그리드 배치 속성을 내용의 논리적 순서가 아니라 시각적 재배열에만 써야 하며, 이 속성들은 음성 같은 비시각 매체의 순서나 키보드 이동의 기본 순서를 바꾸지 않는다고 합니다.
이 글의 방법은 반대로 데이터 순서 자체를 바꾸므로 소스 순서가 1, 4, 2, 5, 3, 6이 됩니다. 눈으로 보는 순서는 맞지만, 화면 낭독기나 키보드로 카드를 넘기는 사용자는 이 순서대로 만나게 됩니다. 순위 정보가 카드 안에 글자로 들어 있다면 큰 문제가 되지 않을 수 있습니다. 그래도 접근성이 중요한 화면이라면 이 점을 알고 선택하셔야 합니다.
선택을 돕기 위해 두 방법을 비교했습니다. CSS 방법은 직접 시도해 보지 않았으므로, 표의 해당 칸은 공식 문서의 설명만 옮겼습니다.
| 방법 | 화면에서 보이는 순서 | Tab·낭독 순서 | 적용할 때 조심할 점 |
|---|---|---|---|
| 데이터 순서를 다시 짜기(이 글) | 위아래 두 줄에서 순위가 가로로 맞음 | 1, 4, 2, 5, 3, 6으로 소스 순서를 따름 | 열·줄 숫자가 코드에 고정됨 |
| CSS의 grid-auto-flow로 채우는 방향 지정 | row는 행 단위, column은 열 단위로 채움(MDN) | 소스 순서 그대로 | dense를 쓰면 DOM 순서와 다르게 보일 수 있음(MDN) |
한계와 다른 방법
3열과 2행이 코드에 숫자로 박혀 있습니다. 화면 크기에 따라 열 개수가 달라지면 이 숫자도 계산해서 넣어야 하는데, 지금은 고정입니다. 가운데 크기 화면에서는 순서가 약간 어긋납니다. 또 위에서 다룬 것처럼 소스 순서가 순위 순서와 다릅니다.
CSS의 grid-auto-flow: column으로 푸는 방법도 있었을 것 같은데, 시도해 보지 않았습니다. 그래서 어느 쪽이 더 낫다고 말씀드릴 수 없습니다. 열 수가 자주 바뀌는 화면이라면 CSS 쪽을 먼저 검토하시고, 열 수가 고정이고 스킨 코드를 크게 바꾸기 어렵다면 이 글의 방법이 빠른 선택입니다. 정리하면 열 수가 고정이고 순위가 눈에 보이는 목록에는 이 글의 방법이, 화면 폭에 따라 열 수가 바뀌는 목록에는 CSS 쪽 검토가 먼저입니다. 어느 쪽이든 바꾼 뒤에는 Tab 키로 카드를 넘겨 보면서 이동 순서가 납득되는지 확인하는 습관을 권합니다. 수정 작업을 AI 도구에 맡길 때 작업 단위를 나누는 방법은 작업 모드를 써 보는 순서를 정리한 글에서, 사용 가능 범위를 먼저 따져 보는 순서는 누가 쓸 수 있고 어디서 시작하는지를 정리한 글에서 볼 수 있습니다.
확인 자료
- MDN, grid-auto-flow (row·column·dense의 채우는 방식, 기본값 row)
- W3C, CSS Grid Layout Module Level 1 (order와 그리드 배치는 시각적 재배열용, 음성·키보드 순서는 그대로)
- MDN, 그리드 레이아웃과 접근성 (시각적 순서와 소스 순서, Tab 순서)