내비게이션은 사이트의 지도이자 손잡이라서, 여기서 길을 잃으면 나머지가 아무리 좋아도 소용이 없습니다. 마크업부터 활성 표시, 드롭다운, 모바일 토글까지 실제 코드로 하나씩 세워 보겠습니다. 아래 예제는 그대로 복사해 확인할 수 있습니다.

21.1 시맨틱한 nav 마크업

내비게이션은 div로 링크를 늘어놓아도 화면에는 똑같이 보이지만, 스크린 리더에는 아무 의미가 없습니다. nav와 목록으로 감싸면 보조기기가 여기가 메뉴라고 읽어 줍니다.


<nav aria-label="주요 메뉴">
<ul>
<li><a href="/">홈</a></li>
<li><a href="/posts">글</a></li>
<li><a href="/about">소개</a></li>
</ul>
</nav>


ul의 기본 불릿과 안쪽 여백은 메뉴에선 거슬리니 지워 두고 가로로 눕힙니다.


nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 4px;
}


가로 한 줄에 링크가 나란히 서고, 의미와 모양을 동시에 챙긴 뼈대가 됩니다.

21.2 현재 위치를 aria-current로 알리기

지금 어느 페이지에 있는지는 색만으로도 눈에 보이지만, 그 색을 못 보는 사람도 있습니다. 현재 링크에 aria-current="page" 를 붙이면 화면과 보조기기 양쪽에 위치를 알립니다.


<li>
<a href="/posts" aria-current="page">글</a>
</li>


이 속성은 클래스와 달리 상태를 나타내는 표준 신호라서, 스타일과 접근성을 한 번에 묶어 둘 수 있습니다.

21.3 활성 링크 스타일

현재 위치는 배경이나 밑줄로 또렷하게 짚어 줍니다. 속성 선택자로 aria-current가 붙은 링크만 골라 칠하면 클래스를 따로 관리할 필요가 없습니다.


nav a {
display: block;
padding: 10px 14px;
color: #4b5563;
text-decoration: none;
border-radius: 6px;
}
nav a:hover {
background: #f3f4f6;
}
nav a[aria-current="page"] {
color: #2563eb;
font-weight: 700;
background: #eff6ff;
}


마우스를 올리면 옅은 회색이 깔리고, 지금 페이지만 파란 글씨에 옅은 파랑 배경으로 도드라집니다. 링크를 block으로 바꿔 둔 덕에 여백 전체가 눌리는 영역이 됩니다.

21.4 드롭다운 메뉴

하위 항목이 많으면 상위 메뉴에 마우스를 올렸을 때 펼쳐지게 합니다. 자바스크립트 없이 hover와 focus-within만으로 열고 닫을 수 있습니다.


<li class="has-sub">
<a href="/docs">문서</a>
<ul class="sub">
<li><a href="/docs/start">시작하기</a></li>
<li><a href="/docs/api">API</a></li>
</ul>
</li>


하위 목록은 평소에 숨겨 두고, 부모에 마우스가 올라오거나 그 안의 링크가 포커스를 받으면 나타나게 합니다.


.has-sub {
position: relative;
}
.sub {
position: absolute;
top: 100%;
left: 0;
min-width: 160px;
background: #fff;
box-shadow: 0 8px 24px rgba(0,0,0,.12);
border-radius: 8px;
padding: 6px;
display: none;
}
.has-sub:hover .sub,
.has-sub:focus-within .sub {
display: block;
}


focus-within을 같이 걸어 둔 게 핵심입니다. 마우스가 없어도 키보드로 부모 링크에 도달하면 하위 메뉴가 열리니, hover만 걸었을 때 생기는 접근 불가 사각지대가 사라집니다.

21.5 모바일 햄버거 토글

좁은 화면에선 메뉴를 접어 두고 아이콘을 눌러 펼치게 합니다. 숨은 체크박스의 상태를 형제 선택자로 읽으면 스크립트 없이 토글이 됩니다.


<input type="checkbox" id="menu" class="menu-toggle">
<label for="menu" class="burger">메뉴</label>
<ul class="mobile-nav">
<li><a href="/">홈</a></li>
<li><a href="/posts">글</a></li>
</ul>


체크박스 자체는 화면에서 숨기고, 체크 여부에 따라 뒤따르는 목록을 접거나 폅니다.


.menu-toggle {
position: absolute;
opacity: 0;
}
.mobile-nav {
max-height: 0;
overflow: hidden;
transition: max-height .25s;
}
.menu-toggle:checked ~ .mobile-nav {
max-height: 320px;
}


label을 누르면 연결된 체크박스가 켜지고, 형제 결합자로 이어진 목록이 스르륵 내려옵니다. max-height에 transition을 걸어 뚝 끊기지 않게 했습니다.

21.6 sticky 내비게이션

글을 길게 내려도 메뉴가 위에 붙어 따라오면 어디로든 바로 이동할 수 있습니다. position 한 줄이면 됩니다.


.site-nav {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
border-bottom: 1px solid #e5e7eb;
}


스크롤이 상단에 닿는 순간 nav가 그 자리에 고정됩니다. z-index를 올려 두어야 본문이 위로 겹쳐 올라오지 않고, 배경색을 줘야 아래 글자가 비쳐 보이지 않습니다.

21.7 본문 바로가기 링크

키보드 사용자는 페이지를 열 때마다 메뉴 링크를 전부 지나야 본문에 닿습니다. 맨 앞에 건너뛰기 링크를 두면 한 번에 본문으로 넘어갑니다.


<a href="#main" class="skip-link">본문 바로가기</a>
<nav> ... </nav>
<main id="main"> ... </main>


평소엔 화면 밖으로 밀어 숨겼다가, 키보드 포커스를 받는 순간 화면 안으로 끌어옵니다.


.skip-link {
position: absolute;
left: -9999px;
top: 8px;
background: #111827;
color: #fff;
padding: 8px 14px;
border-radius: 6px;
}
.skip-link:focus {
left: 8px;
}


마우스 쓰는 사람 눈엔 보이지 않다가, Tab 키를 처음 누르면 왼쪽 위에 툭 나타납니다. 있는 줄도 몰랐던 배려가 여기서 드러납니다.

21.8 키보드 포커스 보이기

메뉴를 키보드로 넘나들 때 지금 어디에 있는지 테두리로 짚어 줘야 합니다. 기본 아웃라인을 지우기만 하고 대신할 표시를 안 주는 게 가장 흔한 사고입니다.


nav a:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
border-radius: 6px;
}


focus-visible는 키보드로 이동할 때만 테두리를 보여 주고 마우스 클릭에는 나서지 않습니다. 덕분에 디자인을 해치지 않으면서 키보드 사용자에게만 길잡이를 줄 수 있습니다.

21.9 정리

좋은 내비게이션은 화려한 게 아니라 어디서든 길을 잃지 않게 하는 것입니다. nav로 의미를 주고, aria-current로 위치를 알리고, focus-within과 체크박스로 스크립트 없이 펼치고, skip-link와 focus-visible로 키보드까지 챙기면 메뉴는 조용히 제 몫을 합니다. 지도가 정확하면 사용자는 지도를 의식조차 하지 않습니다.