본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

링크와 내비게이션 마크업

작성 2026. 10. 20.추가
목차13개 항목
출처와 참고

web.dev의 「Learn HTML」 강좌 중 「Links」와 「Navigation」 모듈을 바탕으로 재구성한 노트다.

링크란

링크는 현재 문서와 다른 자원 사이의 연결이다. <a>, <area>, <form>, <link>가 링크를 만든다. 이 노트는 사용자가 따라가는 <a>와 그 묶음인 내비게이션을 다룬다. <link>는 문서 head와 메타데이터, 링크 텍스트를 접근 가능하게 쓰는 법은 대체 텍스트, 링크, 쉬운 글에 있다.

<a>는 href가 있어야 링크가 된다. href가 없으면 포커스도 받지 않는 자리 표시자일 뿐이다.

href가 가리킬 수 있는 곳

<a href="https://example.com">절대 URL</a>
<a href="../attributes/">상대 URL</a>
<a href="#teachers">같은 페이지의 id="teachers"</a>
<a href="https://example.com#teachers">다른 페이지의 특정 위치</a>
<a href="mailto:hal@example.com?subject=%EB%AC%B8%EC%9D%98">메일 보내기</a>
<a href="tel:+82212345678">전화 걸기</a>
형식동작
절대 URL프로토콜과 도메인을 포함한다. //example.com은 지금 페이지의 프로토콜을 따른다
상대 URL현재 파일 위치 기준(<base>가 있으면 그 기준)
#id같은 문서에서 그 id의 요소로 스크롤한다. 프래그먼트는 서버로 보내지 않는다
#top, #id="top"인 요소가 없으면 페이지 맨 위로 간다
mailto:메일 앱을 연다. ? 뒤에 cc·bcc·subject·body를 &로 잇고 퍼센트 인코딩한다. 주소 없이 제목·본문만 채워 친구에게 보내게 할 수도 있다
tel:기기·OS·설치된 앱에 따라 전화, FaceTime, 연락처 등이 열린다
blob:, data:페이지가 만든 파일을 download와 함께 내려받게 한다(아래 「download: 이동 대신 내려받기」)

다른 앱을 여는 링크는 텍스트로 미리 알린다(「메일 보내기」, 「PDF, 2MB」). CSS의 [href^="mailto:"], [href$=".pdf"] 같은 속성 선택자로 아이콘을 붙여도 좋다.

download: 이동 대신 내려받기

download가 있으면 링크를 열지 않고 내려받는다. 값을 주면 저장할 파일 이름을 제안한다. 같은 출처 URL이나 blob:·data: URL에서만 동작하고, 다른 출처의 파일은 그냥 열린다. 사용자가 만든 내용을 Blob → URL.createObjectURL()로 링크에 넣어 저장시키는 데 자주 쓴다(React에서 음성 녹음하고 파일로 보내기의 녹음 파일 저장이 그 예다).

<a href="/report.pdf" download="2026-보고서.pdf">보고서 받기</a>

어디에 여나: target과 브라우징 맥락

target은 링크한 URL을 어느 브라우징 맥락(탭, 창, <iframe>)에 띄울지 정한다.

값동작
_self현재 맥락에 연다(기본값)
_blank새 맥락에 연다. 보통 새 탭이고, 사용자 설정에 따라 새 창
_parent현재 맥락의 부모에 연다. 부모가 없으면 _self처럼 동작
_top가장 바깥(최상위) 맥락에 연다. 부모가 없으면 _self처럼 동작

<iframe>은 그 자체로 하나의 브라우징 맥락이다. iframe 안의 링크가 기본값 _self로 열리면 iframe 안에서만 페이지가 바뀐다. 전체 창을 이동시키려면 target="_top"을 쓴다.

새 탭으로 열린 페이지는 window.opener로 링크를 연 페이지에 접근할 수 있어, 악의적인 페이지가 원래 탭을 피싱 페이지로 바꿔치기할 수 있다(→ 창과 프레임).

<a href="https://example.com" target="_blank" rel="noopener noreferrer">외부 링크</a>
  • noopener: 새 페이지에서 window.opener를 null로 만든다.
  • noreferrer: noopener 효과에 더해 이전 페이지 주소(Referer 헤더)도 보내지 않는다.

주요 브라우저(크롬 88·파이어폭스 79·사파리 12.1 이후)는 target="_blank"에 noopener를 기본 적용한다. 그래서 noopener는 아주 오래된 브라우저를 위한 안전장치이고, 이전 페이지 주소까지 숨기려면 noreferrer를 명시한다.

이름 붙은 맥락

target에는 밑줄로 시작하지 않는 아무 이름이나 줄 수 있다. 모든 탭(브라우징 맥락)은 이름을 가지며 기본값은 빈 문자열이다.

마크업15번 클릭하면
<a href="/register" target="_blank">이름 없는 새 탭이 15개 열린다
<a href="/register" target="reg">첫 클릭에 reg라는 탭이 열리고, 이후엔 그 탭에서 다시 연다

사용자는 가운데 클릭이나 「새 탭에서 열기」로 언제든 새 탭을 고를 수 있다. _blank로 강제하는 일은 그 선택을 대신하는 것이니 꼭 필요한 링크에만 쓴다(→ 브라우저 기본 동작을 존중하기).

rel과 함께 쓰는 속성

속성·값뜻
rel="nofollow"검색 로봇에게 이 링크를 따라가지 말라고 요청한다
rel="external"다른 사이트로 가는 링크
rel="help"문맥 도움말. 커서 모양 때문에 쓰지 않는다(→ CSS cursor)
rel="prev", rel="next"연재물의 이전·다음 문서
rel="alternate" + hreflang다른 언어 버전. 링크 텍스트가 다른 언어면 lang도 단다
type목적지의 MIME 타입(application/pdf). 참고용일 뿐이지만 형식을 알리는 것은 좋다
ping클릭하면 나열한 HTTPS URL로 POST 요청(본문 PING)을 보낸다. 링크 클릭 추적용
<a href="/fr" hreflang="fr-FR" rel="alternate" lang="fr-FR">atelier d'apprentissage</a>

링크를 쓸 때 지킬 것

원칙이유
링크 텍스트가 목적지를 말한다. 「여기를 클릭」 금지<a>의 내용이 접근 가능한 이름이다. 이미지 링크면 alt가 이름이다
본문 속 링크는 색 말고도 밑줄 같은 표시를 준다색만으로는 구분 못 하는 사용자가 있다
포커스 스타일을 지우지 않는다키보드 사용자가 위치를 잃는다(→ 키보드 접근성과 포커스 관리)
링크 안에 <button>·<input>을, <button>·<label> 안에 링크를 넣지 않는다클릭 가능한 것 안의 클릭 가능한 것은 브라우저마다 동작이 다르고 혼란스럽다
페이지 이동은 <a>, 동작은 <button><a href>는 Enter로 실행되고 새 탭 열기·주소 복사가 된다. <button>은 Space로도 실행된다

JavaScript에서는 document.links가 href가 있는 모든 <a>·<area>를 담은 HTMLCollection이고, HTMLAnchorElement의 href, protocol, hash 같은 속성으로 URL 부분을 읽고 고칠 수 있다. SVG 안에서도 <a href>로 링크를 만든다.

내비게이션: 링크를 묶는 법

여러 링크의 묶음이 내비게이션이다. 어떤 모양이어야 한다는 규칙은 없지만 가장 적은 클릭으로 어디든 갈 수 있고, 직관적이며, 부담스럽지 않아야 한다.

종류무엇범위
전역(global)사이트 최상위 페이지로 가는 링크. 모든 페이지에서 같다사이트
로컬(local)섹션 안의 하위 페이지 링크(사이드바)섹션
목차(table of contents)현재 페이지의 소제목으로 가는 링크페이지
브레드크럼(breadcrumb)사이트 구조에서 현재 페이지의 위치페이지
사이트맵모든 페이지의 계층 목록사이트

공통 마크업

<nav aria-labelledby="toc-title">
  <h2 id="toc-title">이 페이지의 내용</h2>
  <ul role="list">
    <li><a href="#skip">건너뛰기 링크</a></li>
    <li><a href="#breadcrumb">브레드크럼</a></li>
  </ul>
</nav>
규칙이유
묶음은 <nav>navigation 랜드마크가 되어 스크린 리더가 바로 가거나 건너뛴다
링크는 목록(<ul>·<ol>)에 넣는다스크린 리더가 링크 개수를 알려 준다
<nav>가 둘 이상이면 이름을 붙인다보이는 제목이 있으면 aria-labelledby, 없으면 aria-label. 보이는 글은 자동 번역되지만 속성 값은 안 될 수 있다
이름에 「내비게이션」을 넣지 않는다스크린 리더가 역할을 이미 말한다. 「주 메뉴 내비게이션 내비게이션」이 된다
현재 페이지 링크에 aria-current="page"보조 기술이 현재 위치를 알린다. 시각적 구분은 CSS로 따로 준다
화면 크기에 따라 같은 메뉴를 두 벌 만들지 않는다하나를 CSS로 옮긴다. 레이아웃은 바뀌어도 탭 순서는 바뀌면 안 된다

list-style: none이나 display: grid를 준 목록은 사파리 VoiceOver가 목록 의미를 빼는 경우가 있어 role="list"를 덧붙이곤 한다. 목록이라는 사실이 사용자에게 쓸모 있을 때만 붙인다.

건너뛰기 링크

<a href="#main" class="skip-link">본문으로 건너뛰기</a>
…
<main id="main">

포커스 순서의 맨 앞에 두어 키보드 사용자가 매 페이지 반복되는 머리말·메뉴를 건너뛰게 한다. 평소엔 숨겨도 되지만 포커스를 받으면 반드시 보여야 한다(.visually-hidden:not(:focus):not(:active)). 자세한 구현은 접근 가능한 페이지 구조의 「건너뛰기 링크」에 있다.

브레드크럼

<nav aria-label="Breadcrumb">
  <ol role="list">
    <li><a href="/">홈</a></li>
    <li><a href="/learn">Learn</a></li>
    <li><a href="/learn/html">HTML</a></li>
    <li aria-current="page">내비게이션</li>
  </ol>
</nav>
  • 순서가 의미이므로 <ol>을 쓴다.
  • 첫 항목은 사이트 이름이나 로고보다 「홈」이 낫다.
  • 현재 페이지는 넣지 않아도 되고, 넣으면 링크로 만들지 않고 aria-current="page"를 단다.
  • 사이 구분자(›, /)는 CSS li + li::before로 그려 스크린 리더가 읽지 않게 한다. 대비는 다른 글자와 같이 지킨다.
  • 브레드크럼은 사이트 구조를 보여 준다. 서버의 파일 구조와 같을 필요도, 사용자가 실제로 지나온 경로와 같을 필요도 없다. 지나온 경로를 보여 주려면 <nav>에 담되 「브레드크럼」이라 부르지 않는다.

전역 내비게이션과 푸터

전역 내비게이션은 탭, 드롭다운, 플라이아웃, 검색창 등 모양이 어떻든 모든 페이지에 똑같이 있어야 한다. 현재 섹션 표시는 색 대비가 충분해야 한다. 사이트 푸터도 모든 페이지에서 같지만 요구 사항은 없고, 보통 회사 정보·약관·채용·SNS 링크를 둔다.

정리

  • href는 URL, 프래그먼트, mailto:·tel:, blob:·data:까지 가리킨다. 프래그먼트는 서버에 가지 않는다.
  • target="_blank"를 반복하면 탭이 쌓인다. 이름 붙은 target은 같은 탭을 재사용한다.
  • 링크 텍스트는 목적지를 말하고, 링크 안에 다른 인터랙티브 요소를 넣지 않는다.
  • 링크 묶음은 <nav> + 목록, 둘 이상이면 이름을 주고, 현재 위치는 aria-current="page".
  • 건너뛰기 링크는 맨 앞에, 포커스되면 보이게. 브레드크럼은 <ol>과 CSS 구분자.

관련 노트: HTML 기초와 HTML5, 대체 텍스트, 링크, 쉬운 글, 접근 가능한 페이지 구조, 시맨틱 HTML과 문서 구조, 목록과 표 마크업, 브라우저 기본 동작을 존중하기, 창과 프레임: 팝업, iframe, postMessage

원본 기록3

댓글

GitHub 계정으로 댓글을 남길 수 있어요.