들어가며.
여느 때처럼 열심히 스프린트 속에서 개발을 하던 도중, 아이콘 버튼에 툴팁을 추가해야 하는 요구사항을 마주한 적이 있습니다. 하지만, 늘 그렇듯 문제를 마주했는데요, 바로 배포 날짜가 촉박하여 개발할 시간이 없다는 것이었습니다. 핵심 기능 개발 건들이 아직 완료되지 못한 채 쌓여 있는 상황에서, 아무리 사소한 기능이라고는 하지만, 툴팁을 개발하여 적용할 시간에 하나라도 더 우선순위가 높은 기능들을 개발해야 했습니다. 이 상황에서 제가 고를 수 있는 선택지는 2가지였습니다.
배포 범위에서 일단 제외하고 나중에(언젠가) 개발해서 배포하기 vs 어떻게든 툴팁을 만들어 배포하기.
어떻게 해야 할지 고민하며 이것저것 알아보다, HTML의 기본 속성을 이용하여 단 한 줄의 코드만으로 간단하게 툴팁을 구현하여 문제를 해결했습니다. 이 글에서는 어떤 HTML의 기본 속성을 이용하여 툴팁을 구현하였는지 소개하며, 그 동작원리까지 간단히 알아보도록 하겠습니다.
HTML의 title 속성을 아세요?
HTML에는 title이라는 속성이 존재합니다. 이 속성을 이용하면 간단하게 툴팁을 만들 수 있는데요, 사용법은 놀라울 정도로 간단합니다. (진짜 코드 한줄이면, 아니 한 줌이면 됩니다.)
어떤 HTML 요소든 title 속성을 추가하고, 툴팁에 보여주고 싶은 텍스트를 넣기만 하면 됩니다.
<button title="Play (Space)">▶️</button>
이제 이 버튼 위에 마우스를 잠시 올려두면, "Play (Space)"라는 메시지가 담긴 기본 툴팁이 나타납니다.
설마 이게 끝인가요?
네. 그렇습니다.
위 코드를 적용한 채로, 마우스를 버튼에 올려두면 잠시 후 아래와 같이 툴팁이 표시됩니다.
title 속성의 본래 목적은.
사실 title 속성의 원래 목표는 '툴팁을 만드는 것'이 아니었습니다.
툴팁은 브라우저가 이 속성의 본래 목적을 시각적으로 구현한 '결과물'에 가깝습니다.
title 속성의 본래 존재 이유는 "보충 설명 정보(Advisory Information)"를 제공하는 것입니다. 박물관 작품 옆의 작은 설명판을 생각하면 쉽습니다. HTML의 image 태그를 작품이라고 예를 든다면, title 속성은 그 작품 옆에 작게 존재하는 설명판 같은 역할을 하는 것입니다. 이 설명판은 작품 감상에 필수적인 요소는 아니지만, 우리에게 더 많은 정보를 주며 감상을 풍부하게 만듭니다.
잠시 옛날로 돌아가서, 초기 웹을 한 번 떠올려보도록 하죠. 초기 웹은 지금과 달리 많은 인터랙션이 없었습니다. 말 그대로 '웹 문서'의 형태였습니다. 때문에 이 title 속성은 꽤나 요긴하게 사용되었는데요. 예를 들면, 어떤 링크를 누르면 어떤 페이지로 가는지, 혹은 어려운 약어가 있다면 이 약어의 뜻은 무엇인지 등 간단히 알려줄 방법이 필요했었고, 이 title 속성이 그 역할을 해냈습니다.
즉, title 속성은 툴팁의 구현을 위해 만들어졌다기 보다는, 사용자에게 더 많은 맥락이나 부가 정보를 제공해주는 의미론적(semantic) 역할을 위해 만들어졌던 것입니다.
그래서 이게 어떻게 툴팁이 되는 건데?
처음에는 단순히 “브라우저가 툴팁을 DOM 내부에 직접 그려서 렌더링하겠지”라고 생각했습니다. 하지만 놀랍게도, 리서치를 해보니 전혀 그렇지 않았습니다. 실제로는 title 속성으로 만들어지는 툴팁은 DOM이 아니라 플랫폼(UI 계층)이 그려주는 방식이었습니다.
- Windows / Linux / ChromeOS에서는 브라우저의 UI 계층(Views/Aura)이 자체적으로 툴팁 뷰를 만들어 직접 렌더링
- macOS에서는 Cocoa(NSView/AppKit)의 네이티브 툴팁 API를 호출해 시스템 툴팁을 표시
본격적으로 소스 코드를 보며 리서치를 하기 전에 간단한 실험을 해보았는데요, 아래 이미지와 같이 브라우저 바깥 영역에 툴팁이 표시되는 걸 확인할 수 있었습니다. 만약 툴팁이 DOM 내부에 렌더링되는 구조였다면, 브라우저 창 영역을 벗어나 존재할 수는 없었겠죠.
간단한 테스트 결과, 어떤 과정을 통해 툴팁이 렌더링 되는 지 더욱 궁금해졌는데요, 소스 코드를 살펴보기 전에 대략 어떤 식으로 툴팁이 표시될지 생각해보았습니다. 아마 다음과 같은 과정을 거칠 것이라고 생각했습니다.
- html 태그에서 이벤트 감지
- 파싱을 통한 title 텍스트 추출
- os레벨에서 ui 호출하여 툴팁 표시
크로미움 소스 코드를 조금 살펴보니, 실제로 비슷한 과정으로 동작한다는 것을 확인할 수 있었습니다.
소스 코드는 다음 링크에서 확인이 가능합니다. (크로미움 코드 서치)
1단계: HTML 히트테스트 기반 노드 탐색 & 툴팁 후보 전달
브라우저 렌더링 엔진은 “마우스 커서 위치” 같은 좌표를 기준으로 트리 구조를 탐색해서, 포인터가 가리키는 DOM/레이아웃 노드 집합을 찾아냅니다(히트테스트). 이 정보는 HitTestResult 객체에 저장되며, 여기에는 툴팁 후보 텍스트(title 속성 값 등)가 포함됩니다. 그리고 이 HitTestResult를 인자로 하여, 툴팁을 갱신하기 위한 엔트리 함수로 전달하게 됩니다.
// third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
HitTestResult result(request, location);
GetDocument().GetLayoutView()->HitTestNoLifecycleUpdate(location, result);
// 커서 위치 기반으로 노드 집합을 찾아 result에 채움
// third_party/blink/renderer/core/page/chrome_client.h
// 툴팁 갱신 엔트리: 히트테스트 결과(= title 등 후보 포함)를 전달
void UpdateTooltipUnderCursor(LocalFrame&,
const HitTestLocation&,
const HitTestResult&);
2단계: 이벤트 감지 및 타이머
사용자가 요소 위에 마우스를 올리면(mouseover), 브라우저는 바로 툴팁을 띄우지 않고 약 0.5초 정도 기다립니다. 이 타이머/지연 정책은 플랫폼 구현에 따라 조절됩니다(예: Chrome의 Linux/ChromeOS 쪽 UI 계층에선 500ms 대기, 10초 자동 숨김 같은 디폴트 정책이 존재). 마우스 오버(호버) 시 툴팁이 바로 표시되지 않고 딜레이가 있는 이유는, 마우스가 빠르게 스쳐 지나갈 때마다 툴팁이 수시로 반짝 하고 나타나는 현상을 방지하기 위함입니다.
// ui/views/corewm/tooltip_controller.cc
constexpr auto kDefaultShowTooltipDelay = base::Milliseconds(500);
constexpr auto kDefaultHideTooltipDelay = base::Seconds(10);
// 기본 “표시 지연 500ms”, “자동 숨김 10s” 값.
// TooltipController::OnMouseEvent 함수 내부
case ui::EventType::kMouseExited:
SetObservedWindow(nullptr); // → 내부적으로 HideAndReset()
break;
…
case ui::EventType::kMousewheel:
if (state_manager_->IsVisible()) state_manager_->HideAndReset();
break;
// 마우스가 나가거나 휠/키 입력이 오면 즉시 숨김.
// ui/views/corewm/tooltip_state_manager.cc
tooltip_->Show();
if (!hide_delay.is_zero()) {
will_hide_tooltip_timer_.Start(FROM_HERE, hide_delay, this,
&TooltipStateManager::HideAndReset);
}
// 표시 즉시 **will_hide_tooltip_timer_**로 자동 숨김 예약
3단계: 브라우저 View에서 툴팁 UI 호출 & 표시
이제 렌더링 엔진은 툴팁 텍스트를 브라우저 UI 쪽 뷰로 전달해 “이 문자열로 툴팁 보여줘”라는 요청을 하게 됩니다. 플랫폼별로 UI 계층에서 이 문자열을 받아 네이티브(OS) 또는 브라우저 UI 위젯을 통해 표시합니다.
1. 렌더러에서 브라우저 쪽 뷰로 툴팁 텍스트를 전달
// third_party/blink/renderer/core/page/chrome_client.h
void UpdateTooltipUnderCursor(LocalFrame&,
const HitTestLocation&,
const HitTestResult&);
// content/browser/renderer_host/render_widget_host_impl.cc
view_->UpdateTooltipUnderCursor(
GetWrappedTooltipText(tooltip_text, text_direction_hint));
2. 각 플랫폼(OS) 별로 툴팁을 표시
Windows/Linux/ChromeOS의 경우: 툴팁의 표시/숨김은 Views의 TooltipController가 담당
// ui/views/corewm/tooltip_controller.cc
void TooltipController::UpdateIfRequired(TooltipTrigger trigger) {
// ... 중략
state_manager_->Show(observed_window_, wm::GetTooltipText(observed_window_),
tooltip_point, trigger, GetShowTooltipDelay(),
GetHideTooltipDelay());
}
macOS 의 경우: 크게 3 단계를 거쳐 AppKit이 macOS의 네이티브 툴팁을 표시하도록 처리함
// components/remote_cocoa/app_shim/bridged_content_view.mm
// 1) BridgedContentView에서 현재 위치로 텍스트 요청
- (void)updateTooltipIfRequiredAt:(gfx::Point&)location bridge:(...*)bridge {
bridge->host()->GetTooltipTextAt(location, base::BindOnce(..., newTooltipText) {
[self setToolTipAtMousePoint:SysUTF16ToNSString(newTooltipText)];
});
}
// ui/base/cocoa/tool_tip_base_view.mm
// 2) ToolTipBaseView(NSView)에서 실제 툴팁 갱신
- (void)setToolTipAtMousePoint:(NSString*)string {
[self removeAllToolTips];
NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
[self addToolTipRect:wideOpenRect owner:self userData:NULL];
[self _sendToolTipMouseEntered]; // AppKit 툴팁 시스템 트리거
}
// content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm
// 3) 브리지 계층에서 NSView 메서드 직접 호출
// render_widget_host_ns_view_bridge.mm
[cocoa_view_ setToolTipAtMousePoint:base::SysUTF16ToNSString(display_text)];
리서치 결과, title 속성으로 뜨는 툴팁은 단순히 DOM 안에서 그려지는 것이 아니라, 플랫폼 UI 계층이 직접 관리하고 표시하는 요소인 걸 알게되었습니다. 즉, 브라우저는 텍스트만 전달할 뿐, 실제 말풍선을 그리는 주체는 Windows/Linux/ChromeOS에서는 Views/Aura가, macOS에서는 AppKit(NSView)가 되는 것이죠.
언제 활용하면 좋을까.
title 속성을 이용한 툴팁이 어떻게 동작하는 지 살펴보았으니, 이제는 언제 이 기술을 활용하면 좋을 지 알아보겠습니다. 제가 생각하는 이 방식의 장점은 다음과 같습니다.
- 극한의 생산성: 단 몇 글자만 추가하면 되므로 구현 속도가 매우 빠릅니다.
- 뛰어난 성능: OS의 네이티브 기능을 사용하므로 DOM 렌더링을 전혀 유발하지 않아 성능에 부담이 없습니다.
다만 별도의 스타일링이 어렵고, 경우에 따라서는 눈에 잘 띄지 않을 수 있다는 단점이 있습니다. 결국, title을 이용해서 툴팁을 구현한다는 건 명확한 트레이드 오프가 있는 방식이라는 것이죠.
이런 상황에 고려해 보세요
다음과 같은 상황에서는 title 속성을 이용해볼 수 있을 것입니다.
- 아이콘만 있는 버튼에 간단한 설명을 추가할 때
- 너비가 좁아 ...으로 잘리는 텍스트의 전체 내용을 보여줄 때
- 링크의 파일 크기, 이미지의 출처 등 부가적인(non-critical) 정보를 제공할 때
- 개발 리소스가 부족하여 아주 빠르게 툴팁 기능을 구현해야 할 때
이럴 땐 다른 방법을...
다음과 같은 상황에서는 title 속성 대신 직접 툴팁을 구현하거나, Radix UI와 같은 라이브러리를 사용하는 것을 적극 추천합니다.
- 특정 디자인이 적용된 커스텀 스타일의 툴팁이 필요한 경우
- 툴팁 내부에 이미지나 링크 등 복잡한 요소를 넣어야 하는 경우
- 툴팁이 즉시 나타나거나, 사라지는 시간을 제어해야 하는 경우
마무리.
HTML의 title 속성 덕분에 최소한의 노력으로 버튼에 툴팁 표시라는 요구사항을 만족시키고 더 중요한 작업에 집중할 수 있었습니다. 하지만, 사실 숨은 과정이 존재했는데요. 이 기술을 확정적으로 사용하기 까지는 디자이너 및 기획자와 논의하는 과정이 있었습니다. 현재 개발의 진척 상황을 투명하게 공유하고, 마주한 문제와 함께 대안을 제시하는 과정이 있었습니다. 다행히 잘 받아들여졌구요.
저는 주어진 상황과 제약 속에서 문제의 본질을 해결하는 최적의 도구를 잘 선택하는 것이 중요하다고 생각하는데요, 이번 경험을 통해 다시금 깨닫게 되었습니다. 여러분도 혹시 비슷한 상황에 놓인다면, HTML의 title 속성을 한번 떠올려보는 건 어떠실까요?
p.s. 크로미움 소스 코드는 변경이 있기 때문에, 본 내용을 보는 시점에 따라 코드가 달라질 수 있습니다.
'About IT > 웹 개발' 카테고리의 다른 글
커맨드패턴으로 만들어보는 undo/redo (8) | 2025.08.26 |
---|---|
콘스트처럼? 콘스트 단언! as const (3) | 2025.03.16 |
Biome에 대해 아세요? (0) | 2025.02.28 |
익숙한 듯 익숙하지 않은 쿠키(Cookie)의 보안에 대해 알아보자 (1) | 2025.02.03 |
JS로 DOM 꾸미기: 스타일 적용의 6가지 방법 (2) | 2025.01.19 |