본문 바로가기
카테고리 없음

CSS 툴팁 구현 - attr함수, 인접형제선택자, 접근성

by BOOST YOUR INFORMATION 2026. 4. 18.

JS 없이 마우스 오버 시 말풍선 띄우는 방법 3가지 참고 이미지
CSS 툴팁 만들기

 

JavaScript 툴팁이 늦게 뜬 날, CSS 툴팁으로 갈아탄 이유

버튼에 마우스를 올렸는데 아무 반응이 없다는 QA 피드백, 받아보신 적 있습니까? 저는 있습니다. JavaScript로 툴팁을 만들었는데 스크립트 로딩이 조금 늦으면서 생긴 문제였습니다. 그날 이후 CSS만으로 툴팁을 구현하는 방법을 진지하게 파고들었고, 생각보다 쓸 만한 방법이 세 가지나 있다는 걸 알게 됐습니다.

다만 이 글을 시작하면서 한 가지 전제를 먼저 말씀드리고 싶습니다. CSS 툴팁이 JavaScript 툴팁보다 무조건 낫다고 결론 내리는 건 경계해야 합니다. 상황에 따라 CSS가 더 적합한 경우가 있고, JavaScript가 더 적합한 경우도 있습니다. 이 글은 CSS 툴팁 세 가지 방식을 직접 써본 경험과 그 과정에서 마주친 한계를 함께 이야기합니다.

attr() 함수로 HTML 속성 값을 CSS에서 읽는 방법

CSS 툴팁의 첫 번째 방법은 data-tooltip 속성과 attr() 함수를 조합하는 방식입니다. attr() 함수란 HTML 요소에 직접 작성한 속성 값을 CSS가 읽어서 화면에 출력할 수 있도록 해주는 CSS 내장 함수입니다. HTML에 툴팁 텍스트를 직접 써두면 CSS의 content 속성이 그 텍스트를 그대로 가상 요소에 끌어다 씁니다.

처음 이 방식을 접했을 때는 솔직히 낯설었습니다. CSS가 HTML 속성 값을 직접 읽는다는 개념 자체가 생소했거든요. 그런데 막상 프로젝트에 적용해보니 유지보수 면에서 확실히 편했습니다. 툴팁 텍스트를 바꾸고 싶을 때 CSS 파일을 열 필요 없이 HTML의 data-tooltip 값만 수정하면 끝나니까요. 버튼이 열 개든 스무 개든 각자 다른 툴팁 문구를 가질 수 있고, 그 문구 관리는 전부 HTML 안에서 해결됩니다.

::before와 ::after는 가상 요소(pseudo-element)입니다. 가상 요소란 HTML에 별도의 태그를 추가하지 않고도 CSS만으로 DOM에 콘텐츠를 삽입할 수 있는 기능으로, 툴팁 말풍선 본체와 화살표 꼬리를 각각 담당하는 데 자주 쓰입니다. 이 두 가상 요소를 동시에 써야 하는 구조가 되면 코드 관리가 조금 복잡해지는 건 사실입니다. 처음 세팅할 때 position 값을 잡는 데 예상보다 시간이 걸렸습니다.

opacity 전환과 pointer-events: none을 함께 쓰는 것도 이 방식의 핵심입니다. pointer-events: none이란 해당 요소가 마우스 이벤트를 받지 않도록 차단하는 속성으로, 툴팁이 화면에 떠 있는 동안 의도치 않은 클릭이나 호버 이벤트가 툴팁에 걸리는 것을 막아줍니다. display: none 대신 opacity와 transition을 조합하면 툴팁이 부드럽게 나타나고 사라져서 훨씬 자연스러운 인터랙션이 됩니다.

이 방식의 한계도 명확합니다. attr()가 읽어올 수 있는 건 텍스트뿐입니다. HTML이나 링크가 필요한 툴팁에는 이 방식이 맞지 않습니다. 그리고 attr()로 출력된 텍스트는 스크린리더가 자동으로 읽어주지 않기 때문에, 접근성 요건이 있는 프로젝트라면 추가 대응이 필요합니다.

인접 형제 선택자로 툴팁 안에 링크까지 넣기

CSS 툴팁의 두 번째 방법은 :hover 상태에서 인접 형제 선택자를 활용하는 방식입니다. 인접 형제 선택자란 특정 요소 바로 뒤에 오는 형제 요소를 선택하는 CSS 문법입니다. attr() 방식과 달리 툴팁 요소를 HTML 안에 별도로 작성하기 때문에 내부에 링크, 이미지, 복잡한 마크업을 자유롭게 넣을 수 있다는 게 장점입니다.

이 방법을 처음 쓴 건 툴팁 안에 "자세히 보기" 링크를 넣어야 하는 상황이었습니다. 그런데 구현하다가 꽤 당혹스러운 문제를 만났습니다. 툴팁에 마우스 커서를 올리는 순간 툴팁이 사라지는 현상이 생긴 겁니다. 버튼에서 마우스가 벗어나는 순간 :hover가 해제되면서 툴팁이 닫히는 구조였습니다.

해결 방법은 툴팁 영역이 버튼의 :hover 유지 범위 안에 물리적으로 겹치거나, 부모 요소에 :hover를 걸어 두 요소를 함께 감싸는 방식으로 구조를 바꾸는 것입니다. 이 부분을 해결하는 데 생각보다 시간이 꽤 걸렸습니다. CSS 툴팁이 JavaScript보다 단순할 것 같지만, 인터랙션 흐름을 제대로 제어하려면 선택자 구조를 꼼꼼히 짜야 합니다.

이 방식에서 제가 직접 느낀 건, "CSS가 더 간단하다"는 전제가 항상 맞지는 않다는 점입니다. 동작의 단순성이 곧 구현의 단순성을 의미하지 않습니다. 특히 사용자 인터랙션이 관여하는 순간부터 CSS 선택자의 구조적 한계가 드러납니다. 이 한계를 모르고 시작하면 나중에 더 복잡한 수정 작업이 필요해집니다.

툴팁 요소는 반드시 트리거 요소의 바로 다음 형제로 마크업해야 하고, 부모 요소에 :hover를 걸어 버튼과 툴팁을 함께 감싸면 마우스 이동 시 툴팁이 닫히는 문제를 방지할 수 있습니다. opacity와 visibility를 함께 전환하면 transition이 자연스럽게 동작하면서 접근성도 일부 확보됩니다.

title 속성과 data-tooltip을 함께 쓰는 접근성 전략

세 번째 방법은 앞선 두 방식보다 손이 더 가지만, 완성도 면에서는 가장 낫습니다. title 속성을 스크린리더용으로 남겨두고 시각적 말풍선은 data-tooltip으로 따로 구성하는 조합 전략입니다. 스크린리더란 시각 장애인을 포함한 사용자가 화면의 텍스트를 음성으로 들을 수 있게 해주는 보조 기술로, 웹 접근성의 핵심 축입니다.

이 방법을 도입하게 된 계기도 직접적인 피드백이었습니다. 접근성 리뷰에서 "시각적 툴팁은 잘 보이는데 스크린리더로 읽으면 아무것도 안 들린다"는 지적을 받은 겁니다. 당시에는 data-tooltip만 쓰고 있었는데, data-* 속성은 스크린리더가 자동으로 읽어주지 않는다는 걸 그때 처음 제대로 인지했습니다. title 속성은 브라우저 기본 툴팁을 함께 띄워서 시각적으로는 어색하지만, 스크린리더가 이를 읽어주기 때문에 접근성 확보에 유효합니다.

웹 접근성 국제 표준인 WCAG에서는 비텍스트 콘텐츠와 UI 컴포넌트에 텍스트 대안을 제공하도록 명시하고 있습니다. WCAG란 W3C가 제정한 웹 콘텐츠 접근성 지침으로, 다양한 장애가 있는 사용자도 웹을 동등하게 이용할 수 있도록 보장하는 기준입니다. 이 방식을 쓰고 난 뒤 같은 프로젝트에서 접근성 재검토를 받았을 때 해당 항목 지적이 사라졌습니다.

다만 현실적인 과제도 있습니다. title과 data-tooltip을 동시에 관리하면 속성 값이 이중으로 존재하게 되어 텍스트가 달라지는 실수가 생기기 쉽습니다. 그리고 실제 프로젝트에서 이 수준까지 챙기는 경우가 많지 않다는 점도 솔직히 인정할 수밖에 없습니다. 일정이 빡빡한 현장에서는 작동만 하면 넘어가는 경우가 많으니까요. 이 부분은 비판적으로 이야기하자면, 접근성이 "완성도의 옵션"이 아니라 "설계의 기본값"이 되어야 한다는 생각입니다. 현실과 이상 사이의 간극이 크다는 건 알지만, 방향은 분명히 해두는 게 맞습니다.

CSS 툴팁 세 가지 방식을 직접 써본 결과, 어느 방법이 맞는지는 결국 사용 맥락에 달려 있습니다. 단순한 설명 텍스트라면 attr() 함수 방식이 가장 빠르고 관리도 쉽습니다. 툴팁 안에 링크나 복잡한 콘텐츠가 필요하면 인접 형제 선택자 방식으로 가야 하고, 접근성을 제대로 챙겨야 하는 서비스라면 title과 data-tooltip을 함께 쓰는 세 번째 방식이 현실적인 선택입니다. 마지막으로, 모바일 사용자 비중이 높은 환경에서는 :hover 자체가 터치로 동작하지 않기 때문에 CSS 툴팁만으로는 해결이 어렵습니다. 그 경우엔 탭·클릭 토글을 함께 고려하는 편이 낫습니다.


참고:
MDN Web Docs – CSS :hover
MDN Web Docs – CSS attr()
CSS-Tricks – Tooltip
W3Schools – How TO - Tooltip


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 ⚡ 정보 부스터 🚀