HTML button과 a 태그 차이
웹 화면에서 링크와 버튼은 비슷하게 보일 수 있다.
둘 다 클릭할 수 있고, CSS를 적용하면 같은 모양으로 만들 수도 있다.
하지만 HTML에서 <a>와 <button>의 역할은 다르다.<a>는 다른 URL이나 문서의 위치로 이동할 때 사용한다.<button>은 현재 화면에서 어떤 동작을 실행할 때 사용한다.
가장 짧게 구분하면 목적지가 있으면 <a>, 실행할 동작이 있으면 <button>이다.
a 태그의 역할
<a>는 anchor 요소다.href에 지정된 URL로 이동하는 하이퍼링크를 만든다.
<a href="/posts">글 목록으로 이동</a>사용자가 링크를 선택하면 브라우저는 /posts로 이동한다.
이동 대상은 같은 사이트의 페이지일 수도 있고 외부 사이트일 수도 있다.
<a href="https://developer.mozilla.org/">MDN Web Docs</a>같은 문서 안의 특정 위치로 이동할 수도 있다.
<a href="#references">참고 자료로 이동</a>
<h2 id="references">참고 자료</h2>mailto:나 tel:처럼 브라우저가 이해하는 다른 URL scheme도 사용할 수 있다.
<a href="mailto:hello@example.com">이메일 보내기</a>
<a href="tel:+821012345678">전화 걸기</a>핵심은 사용자가 링크를 따라갈 수 있는 실제 목적지가 있다는 점이다.
button 태그의 역할
<button>은 사용자가 활성화해 동작을 실행하는 요소다.
폼 제출, 메뉴 열기, 모달 닫기, 목록 삭제, 재생과 일시 정지 같은 행동에 사용한다.
<button type="button">메뉴 열기</button>type="button"은 자체적인 기본 동작이 없는 일반 버튼을 만든다.
JavaScript 이벤트나 HTML의 선언적 기능을 연결해 원하는 동작을 실행할 수 있다.
<button type="button" id="menu-button">메뉴 열기</button>const menuButton = document.querySelector('#menu-button')
menuButton?.addEventListener('click', () => {
document.querySelector('#menu')?.toggleAttribute('hidden')
})버튼은 주소를 나타내는 요소가 아니다.
현재 문서나 애플리케이션의 상태를 바꾸는 명령에 가깝다.
두 요소의 핵심 차이
두 요소는 화면 모양이 아니라 행동의 의미로 선택한다.
<a>는 다른 URL이나 문서 위치로 이동한다.<button>은 현재 화면에서 동작을 실행한다.<a>의 핵심 속성은 목적지를 담는href다.<button>의 핵심 속성은 행동 종류를 정하는type이다.- 링크는 새 탭 열기, 주소 복사, 북마크 같은 브라우저 기능과 연결된다.
- 버튼은 폼 제출과
disabled상태 같은 form control 기능을 가진다.
글 상세 페이지로 이동한다면 <a>가 맞다.
현재 글을 좋아요 목록에 추가한다면 <button>이 맞다.
이동에 a 태그를 쓰는 이유
브라우저는 실제 링크에 여러 기본 기능을 제공한다.
사용자는 링크를 새 탭에서 열 수 있다.
링크 주소를 복사하거나 북마크할 수도 있다.
검색 엔진과 보조 기술도 href를 가진 링크를 이동 경로로 이해한다.
<a href="/products/keyboard">키보드 상세 보기</a>이동을 버튼과 JavaScript로 구현하면 이런 기본 기능을 잃기 쉽다.
<button type="button" id="product-link">키보드 상세 보기</button>document.querySelector('#product-link')?.addEventListener('click', () => {
window.location.href = '/products/keyboard'
})클릭하면 이동은 되지만 브라우저는 이 요소를 링크로 다루지 않는다.
마우스 오른쪽 메뉴에서 링크 주소를 복사하거나 새 탭으로 여는 일반적인 사용 방식도 기대하기 어렵다.
이동 목적지가 있다면 처음부터 <a href="...">를 사용하는 편이 단순하고 의미도 정확하다.
URL 경로와 검색 조건을 나누는 기준은 URL path parameter와 query string 차이에서 확인할 수 있다.
동작에 button 태그를 쓰는 이유
버튼은 사용자가 명령을 실행한다는 의미를 브라우저와 보조 기술에 전달한다.
<button type="button">장바구니에 담기</button>
<button type="button">알림 닫기</button>
<button type="button">비밀번호 표시</button>세 예시 모두 다른 페이지로 이동하는 것이 아니라 현재 상태를 바꾼다.
버튼은 disabled 속성도 지원한다.
<button type="button" disabled>저장 중</button>비활성화된 버튼은 사용자가 누를 수 없고 일반적인 키보드 초점 대상에서도 제외된다.
작업이 진행 중이거나 필수 입력이 끝나지 않은 상태처럼, 동작을 잠시 막아야 할 때 사용할 수 있다.
링크에는 같은 의미의 네이티브 disabled 속성이 없다.<a disabled>를 작성해도 버튼처럼 자동으로 비활성화되지 않는다.
button type의 종류
<button>의 type은 버튼이 어떤 기본 행동을 하는지 정한다.
type="button"은 일반 동작용 버튼이다.
<button type="button">도움말 열기</button>type="submit"은 연결된 form을 제출한다.
<form action="/search" method="get">
<input type="search" name="q" />
<button type="submit">검색</button>
</form>type="reset"은 form control을 초기값으로 되돌린다.
<button type="reset">입력 초기화</button>실무에서는 사용자가 작성한 내용을 실수로 지울 수 있어 reset 버튼을 신중하게 사용한다.
세 종류를 구분하면 의도하지 않은 폼 제출을 막을 수 있다.
form 내부의 기본 submit 동작
<form> 안에서 button의 type을 생략하면 submit 버튼으로 동작할 수 있다.
<form>
<input name="nickname" />
<button>중복 확인</button>
</form>개발자는 중복 확인만 실행하려고 했지만 버튼을 누르는 순간 form 전체가 제출될 수 있다.
페이지가 새로고침되거나 입력 상태가 사라지는 문제로 이어지기도 한다.
폼을 제출하지 않는 동작이라면 type="button"을 명시한다.
<form>
<input name="nickname" />
<button type="button">중복 확인</button>
<button type="submit">가입하기</button>
</form>버튼의 목적이 코드에서 바로 보이고 브라우저의 기본 동작도 예측하기 쉬워진다.
가짜 버튼으로 만든 a 태그
href="#"를 넣은 링크에 click 이벤트를 연결해 버튼처럼 사용하는 패턴이 있다.
<a href="#" id="delete-button">삭제</a>document.querySelector('#delete-button')?.addEventListener('click', (event) => {
event.preventDefault()
deleteItem()
})겉으로는 동작하지만 링크의 의미와 실제 행동이 맞지 않는다.
JavaScript가 실행되지 않으면 페이지 위쪽으로 이동할 수 있고, 새 탭 열기나 주소 복사도 쓸모없는 결과를 만든다.
스크린 리더는 이 요소를 버튼이 아니라 링크로 안내한다.
URL 이동 없이 삭제만 실행한다면 <button>이 맞다.
<button type="button" id="delete-button">삭제</button>href="javascript:void(0)"도 같은 문제를 가진다.
실제 목적지가 없는 링크를 만들기보다 행동에 맞는 HTML 요소를 선택해야 한다.
가짜 링크로 만든 button 태그
버튼을 눌렀을 때 location.href를 바꿔 페이지를 이동하는 패턴도 피하는 편이 좋다.
<button type="button" id="detail-button">상세 보기</button>document.querySelector('#detail-button')?.addEventListener('click', () => {
location.href = '/products/42'
})실제 목적지가 /products/42로 존재하므로 링크가 더 알맞다.
<a href="/products/42">상세 보기</a>React Router, Next.js 같은 프레임워크를 사용해도 기준은 같다.
클라이언트 라우팅용 Link 컴포넌트는 최종적으로 링크 의미와 브라우저 동작을 유지해야 한다.
현재 상태를 바꾸는 이벤트는 button으로 구현한다.
키보드 동작의 차이
네이티브 링크와 버튼은 키보드에서도 각자 기대되는 방식으로 동작한다.
href가 있는 링크는 보통 Enter로 활성화한다.
버튼은 Enter와 Space로 활성화할 수 있다.
브라우저와 보조 기술은 요소의 의미에 맞춰 초점과 활성화 동작을 제공한다.
그래서 div나 span에 click 이벤트만 붙여 버튼처럼 만드는 방식은 추가 작업이 많이 필요하다.
<div class="button" onclick="save()">저장</div>위 요소는 기본적으로 키보드 초점도 받지 않고 버튼 역할도 전달하지 않는다.tabindex, keyboard event, ARIA role을 모두 직접 보완하는 것보다 네이티브 button을 사용하는 편이 안정적이다.
<button type="button" onclick="save()">저장</button>HTML 요소의 기본 의미를 활용하면 JavaScript 코드도 줄고 접근성도 자연스럽게 따라온다.
링크 텍스트와 버튼 이름
링크 텍스트는 어디로 이동하는지 드러내야 한다.
<a href="/pricing">요금제 안내 보기</a>여기, 자세히처럼 문맥을 떼면 목적지를 알기 어려운 표현은 반복해서 사용하지 않는 편이 좋다.
보조 기술 사용자는 페이지의 링크 목록만 따로 탐색할 수 있기 때문이다.
버튼 텍스트는 어떤 동작을 실행하는지 드러내야 한다.
<button type="button">댓글 삭제</button>아이콘만 있는 버튼에도 접근 가능한 이름이 필요하다.
<button type="button" aria-label="검색창 닫기">
<svg aria-hidden="true" viewBox="0 0 24 24">
<!-- icon path -->
</svg>
</button>화면에 보이는 아이콘은 숨기지 않더라도, 보조 기술이 버튼 목적을 알 수 있는 텍스트나 aria-label을 제공해야 한다.
새 탭 링크
외부 문서를 새 탭에서 열어야 한다면 anchor의 target을 사용할 수 있다.
<a
href="https://developer.mozilla.org/"
target="_blank"
rel="noopener noreferrer"
>
MDN Web Docs 새 탭에서 열기
</a>새 탭이나 새 창이 열리는 동작은 사용자에게 예상하지 못한 변화가 될 수 있다.
링크 텍스트나 아이콘을 통해 새 탭에서 열린다는 사실을 알리는 편이 좋다.
현재 브라우저는 target="_blank" 링크를 안전하게 다루는 기본 동작을 강화해 왔다.
그래도 rel을 명시하면 외부 문서와의 관계와 의도가 코드에 드러난다.
모든 외부 링크를 무조건 새 탭으로 열 필요는 없다.
사용자가 현재 문맥에서 자연스럽게 이동할 수 있다면 일반 링크 동작을 유지하는 편이 낫다.
다운로드 링크
파일을 내려받는 이동도 anchor가 담당할 수 있다.
<a href="/files/guide.pdf" download>가이드 PDF 다운로드</a>이 링크는 다운로드할 실제 resource URL을 가진다.
따라서 다운로드 버튼처럼 보여도 의미상 anchor가 자연스럽다.
서버에서 파일을 생성하는 요청을 먼저 보내야 하거나, 현재 화면의 데이터를 가공해 파일을 만드는 복잡한 동작이라면 button으로 시작할 수도 있다.
핵심은 클릭 결과가 이미 존재하는 resource로 이동하는지, 현재 애플리케이션에서 새 동작을 실행하는지다.
disabled 상태의 선택
버튼은 disabled를 사용해 동작을 막을 수 있다.
<button type="submit" disabled>필수 항목을 입력하세요</button>링크에는 네이티브 disabled 상태가 없다.
이동할 수 없는 상태라면 링크를 억지로 비활성화하기보다 왜 목적지가 아직 제공되지 않는지 UI 구조를 다시 보는 편이 좋다.
필요하다면 링크 대신 현재 상태를 나타내는 텍스트를 렌더링할 수 있다.
<span aria-current="page">결제 완료</span>aria-disabled="true"만 anchor에 추가해도 클릭과 키보드 동작이 자동으로 막히지는 않는다.
ARIA는 의미를 전달하지만 네이티브 행동을 대신 구현해 주지 않는다.
요소 중첩의 주의점
링크 안에 버튼을 넣거나 버튼 안에 링크를 넣으면 안 된다.
<!-- 피해야 하는 구조 -->
<a href="/products/42">
상품 보기
<button type="button">좋아요</button>
</a>두 interactive element가 겹치면 클릭 대상과 키보드 초점의 의미가 불분명해진다.
카드 전체 이동과 카드 안의 좋아요 동작이 모두 필요하다면 두 요소를 형제 관계로 분리하는 편이 낫다.
<article class="product-card">
<a href="/products/42">상품 보기</a>
<button type="button">좋아요</button>
</article>시각적으로 겹쳐야 한다면 HTML 구조는 분리한 채 CSS layout이나 position으로 배치할 수 있다.
CSS position relative와 absolute 차이에서 기준 요소를 잡는 방법을 정리했다.
같은 모양으로 스타일링하는 기준
링크를 버튼처럼 보이게 만들 수 있고 버튼을 텍스트 링크처럼 보이게 만들 수도 있다.
시각적 모양이 HTML 의미를 결정하지는 않는다.
.primary-action {
display: inline-flex;
min-height: 44px;
align-items: center;
justify-content: center;
padding: 0 16px;
border-radius: 4px;
background: #1769e0;
color: #fff;
}<a class="primary-action" href="/signup">회원가입 페이지로 이동</a>
<button class="primary-action" type="submit">회원가입 완료</button>두 요소는 같은 디자인을 공유하지만 첫 번째는 이동이고 두 번째는 form 제출이다.
클릭 영역을 키우는 padding과 요소 바깥 간격의 차이는 CSS margin과 padding 차이에서 확인할 수 있다.
inline 요소의 크기 동작은 CSS display block inline inline-block 차이와 연결해서 보면 좋다.
선택 기준
다른 페이지, 문서, 파일, 이메일 주소, 전화번호처럼 실제 목적지가 있다면 <a>를 사용한다.
<a href="/account/settings">계정 설정으로 이동</a>현재 화면에서 열기, 닫기, 저장, 삭제, 토글, 제출 같은 행동을 실행한다면 <button>을 사용한다.
<button type="button">설정 창 열기</button>선택이 어려울 때는 클릭한 뒤 브라우저 주소가 의미 있는 목적지로 바뀌는지 생각하면 된다.
주소로 표현할 수 있고 공유하거나 새 탭에서 열 수 있어야 한다면 링크에 가깝다.
주소 이동 없이 애플리케이션 상태가 바뀐다면 버튼에 가깝다.
정리
<a>는 이동을 위한 HTML 요소다.
실제 href를 제공하면 브라우저의 새 탭 열기, 주소 복사, 북마크 같은 링크 기능을 사용할 수 있다.
<button>은 동작을 실행하는 HTML 요소다.
일반 동작은 type="button", form 제출은 type="submit"으로 목적을 명시하는 편이 안전하다.
링크를 버튼처럼 꾸미거나 버튼을 링크처럼 꾸밀 수는 있다.
하지만 CSS 모양과 HTML 의미는 별개다.
목적지가 있으면 <a>, 실행할 동작이 있으면 <button>이다.
이 기준을 지키면 브라우저 기본 기능과 키보드 접근성을 적은 코드로 함께 얻을 수 있다.
참고 자료














