빌드노트
flex order로 버튼을 옮겼는데 Tab 초점은 원래 순서로 간다
flex order로 바뀐 화면 순서와 Tab 순서를 버튼 세 개에서 비교했다. DOM 목록·실제 좌표·키보드 초점을 대조해, 단계의 순서를 CSS 대신 마크업에서 맞추는 방법을 설명한다.
flex의 order로 버튼을 배송→결제→확인 순서로 배치했는데, Tab 초점은 결제→확인→배송으로 움직일 수 있다. order는 화면의 배치를 바꾸며, 기본 순차 탐색을 쓰는 버튼의 DOM 순서까지 바꾸지는 않는다. 배송부터 읽고 조작해야 하는 단계라면, 버튼을 만드는 마크업부터 배송→결제→확인으로 고친다.
Flexbox 명세의 순서와 접근성 절 (새 탭에서 열림)은 논리적 순서와 시각적 배치를 구분한다. 여기서 DOM 순서는 개발자 도구의 Elements에 나타나는 요소의 순서다. 화면에서 왼쪽에 보인다는 사실만으로 Tab이 먼저 도착한다고 판단할 수는 없다.
DOM에서는 몇 번째이고, 화면에서는 어디에 있는가
먼저 문제가 있는 버튼 그룹을 개발자 도구에서 찾는다. 아래 코드는 .steps의 직접 자식 버튼을 DOM 순서대로 나열하고, 각 버튼의 화면 왼쪽 좌표와 적용된 order를 읽는다. 자기 페이지에서는 .steps > button을 실제 그룹의 선택자로 바꾼다.
Array.from(document.querySelectorAll('.steps > button'), button => ({
text: button.textContent.trim(),
left: button.getBoundingClientRect().left,
order: getComputedStyle(button).order,
tabIndex: button.tabIndex
}))
아래 합성 페이지에서는 배열의 항목이 결제→확인→배송 순서인데, left는 각각 136, 248, 24였다. 단위는 뷰포트 왼쪽을 기준으로 한 CSS px다. 배송이 DOM에서는 마지막이지만 화면에서는 맨 왼쪽에 있다는 뜻이다. 결제·확인의 order는 "0", 배송은 "-1"이었고 세 버튼의 tabIndex는 모두 0이었다.
좌표 조회만으로 Tab 순서를 관찰한 것은 아니다. 페이지에서 결제 버튼을 클릭해 시작점을 잡고 Tab으로 확인→배송을 지나는지 초점 표시를 본다. Shift+Tab으로 돌아오는 순서도 확인한다. 중간 버튼에서 시작했는지, 그룹에 들어오는 첫 초점부터 보았는지를 함께 적어야 비교가 맞는다.
같은 화면을 세 가지 조건으로 비교했다
문제를 따로 확인하려면 다음 내용을 빈 HTML 파일에 저장해 브라우저로 연다. 서버나 라이브러리는 필요 없다. 배송 버튼에만 order: -1을 주어, 마크업의 마지막 버튼을 화면 앞으로 옮긴 예제다. 버튼은 실제 주문을 실행하지 않는다.
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>세 버튼의 배치와 Tab 순서 비교</title>
<style>
body { margin: 24px; }
.steps { display: flex; gap: 12px; }
.steps > button { flex: 0 0 100px; height: 44px; }
.steps > button:focus { outline: 3px solid #005fcc; outline-offset: 3px; }
.shipping { order: -1; }
</style>
<div class="steps">
<button type="button">결제</button>
<button type="button">확인</button>
<button type="button" class="shipping">배송</button>
</div>
Windows 11의 headless Chrome에서 이 페이지의 버튼 좌표와 실제 Tab 입력 후 활성 요소를 기록했다. 브라우저의 사용자 에이전트 버전 표시는 153.0.0.0이었다. 각 조건마다 새로 연 문서의 본문에서 시작해 Tab을 세 번 보냈다. 960×600과 390×600 뷰포트 모두 같은 결과였다.
order를 제거한 비교: DOM·왼쪽부터 본 화면·Tab 순서가 모두 결제→확인→배송이었다. Tab이 도착한 버튼의 왼쪽 좌표는 24→136→248이었다.- 위 HTML의
order: -1: DOM과 Tab은 결제→확인→배송, 화면은 배송→결제→확인이었다. Tab 초점은 136→248→24로 이동했다. 오른쪽 끝의 확인 다음에 왼쪽 끝의 배송으로 돌아갔다. - DOM을 고치고
order를 제거한 비교: DOM·화면·Tab 모두 배송→결제→확인이 됐다. Tab 좌표도 24→136→248로 바뀌었다.
세 조건에서 버튼의 경계 상자는 모두 폭 100px, 높이 44px, 위쪽 좌표 24px였다. 초점 이동 차이는 버튼이 줄바꿈되거나 크기가 달라져서 생긴 것이 아니었다. 마지막 버튼까지 간 뒤 Shift+Tab을 두 번 보냈을 때도 각 조건의 DOM 순서를 거꾸로 따라갔다.
MDN의 flex 항목 순서 설명 (새 탭에서 열림)에 따르면 order는 값이 작은 그룹부터 배치하며 기본값은 0이다. 따라서 배송의 -1은 나머지 두 버튼보다 앞에 배치된다. 이 배치 변경과 기본 Tab 탐색은 서로 다른 순서를 사용할 수 있다.
단계의 순서는 마크업에서 고친다
이 예제의 의도한 단계가 배송→결제→확인이라면, 위 HTML의 버튼 그룹을 다음처럼 바꾸고 .shipping { order: -1; } 선언을 제거한다. .steps와 버튼 크기·초점 표시 CSS는 그대로 둘 수 있다.
<div class="steps">
<button type="button">배송</button>
<button type="button">결제</button>
<button type="button">확인</button>
</div>
실제 서비스에서는 버튼을 만드는 템플릿이나 렌더링 배열의 순서를 바꾼다. 화면에서 위치를 옮긴 뒤 끝내지 말고, 새로 렌더링된 DOM과 Tab 순서를 다시 대조한다. 원하는 화면이 이미 배송→결제→확인이었다면 외관은 같아도 키보드 탐색 결과는 달라질 수 있다.
양수 tabindex로 배송에 1, 결제에 2, 확인에 3을 매기면 별도의 초점 순위를 관리하게 된다. MDN의 tabindex 설명 (새 탭에서 열림)에 따르면 양수 항목은 기본 순서의 항목보다 먼저 탐색된다. 주변 링크와 입력란에도 영향을 주므로 이 문제의 기본 수정으로 삼지 않는다. 네이티브 버튼은 원래 Tab 탐색에 들어가며, 세 버튼에 tabindex="0"을 덧붙여도 DOM 순서를 원하는 단계로 고친 것이 되지는 않는다.
이 비교는 양수 tabindex, 초점을 옮기는 JavaScript, 비기본 reading-flow가 없는 조건이다. 컨테이너의 계산된 reading-flow는 normal이었다. reading-flow 문서 (새 탭에서 열림)는 다른 값으로 순차 탐색 순서를 바꿀 수 있다고 설명한다. 따라서 모든 CSS가 Tab 순서에 영향을 줄 수 없다고 일반화하지는 않는다. 자기 페이지의 결과가 다르면 해당 속성, tabindex, disabled와 초점 이동 코드를 함께 확인한다.
직접 실행한 것은 가로 한 줄, 왼쪽에서 오른쪽으로 배치한 버튼 세 개의 좌표와 키보드 초점이다. 390px 비교도 데스크톱 브라우저의 뷰포트 변경이며 실제 휴대전화 검증은 아니다. 다른 브라우저 엔진과 스크린리더는 실행하지 않았으므로 낭독 순서를 관찰했다고 말할 수 없다. 단계의 순서를 고쳤는지는 이 범위에서 확인한 것처럼 DOM·화면 위치·실제 Tab 이동을 나란히 놓고 판단한다.