클릭 가능한 요소에 cursor pointer를 써야 할까?
왜 cursor-pointer를 안 쓸까? 그리고 나는 왜 쓸까

발단
프로젝트에서 UI 컴포넌트를 만들다 보면 클릭 가능한 요소에 cursor: pointer를 넣는 게 당연하다고 생각했어요.
버튼, 카드, 탭, 드롭다운 항목 등등... 클릭할 수 있으면 손가락 커서가 나와야 "아, 이거 누를 수 있구나"라고 느끼니까요.
그런데 자주 쓰는 UI 컴포넌트 라이브러리인 shadcn/ui를 보니까 버튼 위에 마우스를 올려도 커서가 안 바뀌더라고요.
/* 기본 브라우저 동작 */
button {
cursor: default; /* ← 화살표 그대로! */
}내부 컴포넌트에서도 cursor-default를 사용하고 있었어요. 근데 찾아보니까 이게 의도된 동작이었어요. 왜 이런 걸까요?
W3C 스펙이 말하는 cursor: pointer
W3C CSS 스펙에서 cursor: pointer의 정의를 보면 이렇게 되어 있어요.
The cursor is a pointer that indicates a link.
네, 원래 pointer는 하이퍼링크를 위한 커서였어요. 버튼이나 체크박스 같은 폼 요소를 위한 게 아니었죠.
이 스펙을 근거로 하는 원칙론자들의 주장은 이래요.
- 버튼은 생김새 자체로 클릭 가능함을 전달해야 한다
- 커서 변경에 의존하는 건 시각적 디자인이 부족하다는 신호다
- 네이티브 OS에서 버튼 위에 마우스를 올려도 커서가 안 바뀐다
실제로 macOS에서 Finder 상단의 버튼에 마우스를 올려보면 화살표 커서 그대로에요. Windows도 마찬가지고요. 네이티브 앱에서는 버튼 = pointer가 아닌 거죠.
이 입장을 대표하는 글이 Adam Silver의 "Buttons shouldn't have a hand cursor"에요.
반대편: 웹은 OS가 아니다
근데요, 저는 이 주장에 동의하기 어려웠어요.
이유는 간단해요. 웹 사용자들은 이미 pointer = 클릭 가능으로 학습되어 있거든요.
Chris Coyier(CSS-Tricks 창립자)는 Give Clickable Elements a Pointer Cursor라는 글에서 클릭 가능한 모든 요소에 cursor: pointer를 적용하는 CSS 스니펫을 직접 공유했어요. 웹에서는 pointer가 클릭 가능을 의미한다는 입장이죠.
크게 두 진영으로 갈려요.
한쪽은 원칙론이에요. W3C 스펙과 OS 네이티브 동작을 근거로 "버튼은 생김새만으로 클릭 가능함을 알려야 한다"고 봐요. Adam Silver가 대표적인 입장인데, 약점은 정작 사용자가 "이거 클릭되나?" 하고 망설일 수 있다는 거죠.
반대쪽은 실용주의예요. 사용자가 이미 몸에 익힌 기대를 근거로 "웹은 OS가 아니니까 관습도 다르다"고 보는 쪽이고요. Chris Coyier가 여기에 해당하는데, 대신 스펙과는 안 맞는다는 점이 걸려요.
저는 실용주의 쪽이에요. 이유를 좀 더 풀어볼게요.
어포던스(Affordance) 이야기
UX에서 어포던스란 "이 물건을 어떻게 사용해야 하는지 직관적으로 알려주는 단서"를 뜻해요.
문 손잡이를 떠올려보면 쉬워요.
- 둥근 손잡이 → "돌려야겠다"
- 평평한 판 → "밀어야겠다"
- 손잡이 없음 → "...어떻게 여는 거지?"
웹도 똑같아요. 그림자가 진하게 들어간 버튼은 "누를 수 있겠다" 싶고, 밑줄 쳐진 텍스트는 "링크구나" 하죠. 근데 그림자도 밑줄도 없이 그냥 텍스트처럼 보이는 버튼이면? "...이거 누르는 건가?" 하고 한 번 멈칫하게 돼요.
원칙론자들은 시각적 어포던스(색상, 그림자, 호버 효과 등)만으로 충분하다고 해요. 맞는 말이에요. 이상적으로는요.
근데 현실의 웹 UI는 이상적이지 않아요.
현실의 웹 UI
요즘 디자인 트렌드를 보면:
// 요즘 흔한 미니멀 버튼
<button className="text-sm text-gray-600 hover:text-gray-900">
필터 초기화
</button>
// Ghost 버튼
<button className="border border-gray-200 rounded-md px-3 py-1">
취소
</button>
// 카드 전체가 클릭 가능
<div onClick={handleClick} className="rounded-lg p-4 bg-white shadow-sm">
<h3>프로젝트 이름</h3>
<p>설명...</p>
</div>이런 요소들은 시각적 어포던스가 약해요. 배경색도 없고, 그림자도 미미하고, 텍스트처럼 보이는 버튼도 많죠.
이런 상황에서 cursor: pointer마저 없으면? 사용자는 진짜로 "이거 누르는 건가?"하고 망설여요.
특히 커스텀 스타일 컴포넌트가 많아진 요즘은 커서 피드백이 더 중요해졌다고 생각해요.
shadcn/ui에서 실제로 일어난 일
이건 제 추측이 아니라 실제로 shadcn/ui GitHub에서 벌어진 일이에요. cursor-pointer 관련 이슈가 수없이 올라왔어요. #7501 "button cursor-pointer or not?", #7223 "Button does not have cursor-pointer by default" 같은 글이 계속 올라왔고, Tailwind v4 관련 이슈(#6843)나 toggle·checkbox에 pointer가 없다는 글(#7279)까지 합치면 정말 많았어요.
이렇게까지 이슈가 올라온 데는 이유가 있어요.
Tailwind CSS v4의 변경
Tailwind CSS v4에서 버튼의 기본 커서가 cursor: pointer에서 cursor: default로 변경됐어요.
Tailwind v3까지는 preflight(기본 리셋)에서 버튼에 cursor: pointer를 자동 적용해줬는데, v4부터는 브라우저 네이티브 동작을 따르도록 바뀐 거예요.
shadcn/ui가 Tailwind v4를 채택하면서 자연스럽게 모든 버튼에서 pointer가 사라졌고, 이슈가 한꺼번에 올라온 거죠.
유지보수자의 입장은 "프로젝트 레벨에서 글로벌로 추가하라"는 방향이에요.
/* globals.css */
@layer base {
a,
button,
[role='button'],
input[type='submit'],
input[type='reset'],
input[type='button'],
label,
select,
summary {
cursor: pointer;
}
}내 결론: 웹에서는 pointer가 맞다
이래저래 따져봤지만 저는 그냥 넣는 쪽이에요.
일단 사람들이 20년 넘게 pointer = 클릭 가능으로 익혀왔잖아요. 이걸 "스펙에 안 맞으니까"라고 빼버리는 건 사용자가 아니라 스펙을 위한 결정이라고 생각해요. 게다가 버튼을 아무리 잘 그려놔도 커서까지 바뀌면 "아, 이거 누르는 거구나" 하고 한 번 더 확신이 들거든요. 있으면 좋고 없으면 괜히 불안한, 일종의 보험인 셈이죠. 미니멀한 디자인일수록 이 보험이 더 든든하고요.
접근성도 무시 못 해요. 호버할 때 색이 살짝 바뀌는 걸 모든 사람이 알아채는 건 아니거든요. 그에 비하면 커서 변화는 거의 누구나 알아채는 피드백이에요. 무엇보다 cursor: pointer 한 줄 넣는 데 드는 비용이 사실상 없어요. 성능에 영향이 있는 것도, 부작용이 있는 것도 아니니까요. 반대로 안 넣었을 때의 UX 손실은 은근히 체감되고요.
실전에서 적용하는 방법
프로젝트 전체에 한 번에 적용하고 싶다면 globals.css에 아래를 추가하면 돼요.
@layer base {
/* 클릭 가능한 모든 요소에 pointer 적용 */
a,
button,
[role='button'],
input[type='submit'],
input[type='reset'],
input[type='button'],
label[for],
select,
summary,
[onclick] {
cursor: pointer;
}
/* 비활성 상태는 명확하게 */
[disabled],
[aria-disabled='true'] {
cursor: not-allowed;
}
}컴포넌트별로 적용한다면
// 클릭 가능 → pointer
<Button className="cursor-pointer">저장</Button>
<Card className="cursor-pointer" onClick={handleClick}>...</Card>
<TabsTrigger className="cursor-pointer">탭 1</TabsTrigger>
// 비활성 → not-allowed
<Button disabled className="cursor-not-allowed">저장</Button>
// 클릭 불가 → 기본 커서 (아무것도 안 해도 됨)
<div>그냥 텍스트</div>마무리
스펙의 원래 의도나 OS 동작을 존중하는 것도 일리는 있어요. 그치만 결국 UX는 스펙이 아니라 사용자를 위한 거잖아요. 사람들이 20년 넘게 몸에 익힌 습관을 "스펙에 안 맞아서"라는 이유로 빼는 게 과연 좋은 선택일까 싶어요.
사실 답은 shadcn 이슈창에 이미 나와 있는 것 같아요. "커서가 안 바뀌어서 누를 수 있는지 모르겠다"는 글이 계속 올라오는 걸 보면요. 그래서 저는 그냥, 커서 넣을래요.
참고 자료
- Give Clickable Elements a Pointer Cursor - CSS-Tricks
- shadcn/ui #7501 - button cursor-pointer or not?
- shadcn/ui #7223 - Button does not have cursor-pointer by default
- shadcn/ui #6843 - Cursor pointer not working in Tailwind v4
- shadcn/ui PR #7977 - Add cursor-pointer to Button