Tooltip
Prompt
Your task is to build a tooltip using HTML, CSS, and JavaScript. A tooltip is a small popup that appears when the user hovers over an element, providing additional context or information.
The tooltip should be precisely positioned and centered above the trigger element. You must use getBoundingClientRect() to calculate the exact position of the tooltip relative to the trigger element. If you are not familiar with getBoundingClientRect(), you can look it up on MDN or use AI assist to understand how it works.
Requirements
- When the user hovers over a trigger element, a tooltip should appear above it.
- The tooltip must be horizontally centered relative to the trigger element.
- Use
getBoundingClientRect()to calculate the position of the tooltip. - The tooltip should have an arrow pointing down toward the trigger element.
- The tooltip should be removed when the mouse leaves the trigger element.
Example

Playground
You can use element.getBoundingClientRect() to get the position and dimensions of any element on the page. It returns an object with top, left, right, bottom, width, and height properties. This is how you will calculate where to place the tooltip.
To center the tooltip horizontally above the trigger element, you need to calculate the center point of the trigger and then offset the tooltip by half its own width. The formula is: triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2).
To create an arrow on the tooltip pointing down toward the trigger, you can use a CSS ::after pseudo-element with the border trick. Set all borders to transparent except the top border, which should match the tooltip background color. Position it at the bottom center of the tooltip.
Solution
Explanation
I will walk you through this interview challenge which has been asked at LinkedIn. We will go through the solution step by step. During this process, I will show you what an ideal solution looks like, where candidates go wrong and how interviewers evaluate your approach.
Now before we start coding, it is very important to thoroughly read the prompt and understand the requirements. The key thing here is that the tooltip must be centered above the trigger element using getBoundingClientRect(). This is not just a CSS-only tooltip, the interviewer specifically wants to see that you can calculate positions using JavaScript. If anything is not clear, discuss it with the interviewer before you start coding.
Now, let's start solving.
Step 1: HTML Structure
Let's start with the HTML. We need a trigger element that the user can hover over, and the trigger needs to carry its tooltip text. We will use an anchor tag (<a>) as the trigger because in real applications tooltips are most commonly seen on links and interactive text. We store the tooltip text in a data-tooltip attribute.
index.html
<div class="wrapper">
<a
href="javascript:void(0)"
class="trigger"
data-tooltip="Tooltip text!"
>
This is a link
</a>
</div>You might notice href="javascript:void(0)" on the link. This is just a playground workaround to prevent the link from navigating when clicked, which would cause errors in the sandboxed environment. This is not something you need to worry about or do in an actual interview, it has no relevance to the tooltip logic.
We are using a data-tooltip attribute to store the tooltip text. This is a clean and scalable approach because we can make any element a tooltip trigger just by adding a data-tooltip attribute to it. We do not need to hard-code tooltip text in JavaScript or create separate elements for each tooltip.
Interview Tip
Using data-* attributes is a very common pattern in production codebases. It keeps the HTML declarative and the JavaScript generic. When the interviewer sees this, they know you understand how to build components that are easy to extend and maintain. If you hard-code the tooltip text in your JavaScript, it becomes tightly coupled to specific elements and harder to reuse.
Step 2: Basic Tooltip Creation on Hover
Now let's write the JavaScript. We need to listen for mouseenter and mouseleave events on elements that have the data-tooltip attribute. On mouseenter, we will create a tooltip element, and on mouseleave, we will remove it.
index.js
import './styles.css';
function showTooltip(triggerElement) {
const tooltipText =
triggerElement.getAttribute('data-tooltip');
if (!tooltipText) return;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = tooltipText;
document.body.appendChild(tooltip);
triggerElement._tooltip = tooltip;
}
function hideTooltip(triggerElement) {
const tooltip = triggerElement._tooltip;
if (tooltip) {
tooltip.remove();
triggerElement._tooltip = null;
}
}
const triggers = document.querySelectorAll(
'[data-tooltip]'
);
triggers.forEach((trigger) => {
trigger.addEventListener('mouseenter', () =>
showTooltip(trigger)
);
trigger.addEventListener('mouseleave', () =>
hideTooltip(trigger)
);
});Let's walk through what is happening here.
Unlike the calculator question, we do not attach these functions to window. The window object is only needed when functions are called from inline onclick attributes in HTML. Here we are using addEventListener entirely within index.js, so the functions stay as regular module-scoped functions and call each other directly.
In showTooltip, we first read the tooltip text from the data-tooltip attribute using getAttribute. If there is no text, we return early. Then we create a new div element, give it the class tooltip, set its text content, and append it to the document.body. We store a reference to this tooltip on the trigger element using triggerElement._tooltip so we can find it later when we need to remove it.
In hideTooltip, we look for the stored tooltip reference on the trigger element and remove it from the DOM. We also set the reference to null to clean up.
We use querySelectorAll('[data-tooltip]') to find all elements that have the data-tooltip attribute and attach event listeners to each one.
Common Pitfalls
- A common mistake candidates make is creating the tooltip element once and trying to show/hide it with CSS
display: noneanddisplay: block. While this works for a single tooltip, it breaks when you have multiple triggers because they would all share the same tooltip element. Creating and removing the tooltip dynamically is cleaner and avoids this problem entirely. - Some candidates forget to store a reference to the tooltip and then try to find it using
document.querySelector('.tooltip')when removing. This breaks when there are multiple tooltips visible at the same time. Always store a direct reference.
Step 3: Positioning with getBoundingClientRect()
This is the core of the challenge. Right now the tooltip just appears somewhere on the page. We need to position it precisely above the trigger element and center it horizontally. This is where getBoundingClientRect() comes in.
index.js (updated showTooltip)
import './styles.css';
const TOOLTIP_GAP = 8;
function showTooltip(triggerElement) {
const tooltipText =
triggerElement.getAttribute('data-tooltip');
if (!tooltipText) return;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = tooltipText;
document.body.appendChild(tooltip);
const triggerRect =
triggerElement.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const top =
triggerRect.top -
tooltipRect.height -
TOOLTIP_GAP +
window.scrollY;
const left =
triggerRect.left +
triggerRect.width / 2 -
tooltipRect.width / 2 +
window.scrollX;
tooltip.style.top = `${top}px`;
tooltip.style.left = `${left}px`;
tooltip.style.opacity = '1';
triggerElement._tooltip = tooltip;
}Let's break down the positioning logic because this is what the interviewer is really testing.
getBoundingClientRect() returns the position and size of an element relative to the viewport. It gives us top, left, right, bottom, width, and height. We call it on both the trigger element and the tooltip element because we need measurements from both.
What is a Rect?
You will notice we named our variables triggerRect and tooltipRect. The "Rect" in getBoundingClientRect stands for Rectangle. Every element on a web page is rendered as a rectangle, and this method returns a DOMRect object describing that rectangle's position and dimensions. We name our variables with the Rect suffix to make it clear that they hold rectangle measurement data, not the elements themselves. This is a common naming convention you will see in codebases that deal with positioning and layout.
For the vertical position (top), we take the trigger's top (the top edge of the link), subtract the tooltip's height (so the tooltip sits above the link), and subtract TOOLTIP_GAP (8 pixels of spacing between the tooltip and the link). We also add window.scrollY because getBoundingClientRect() returns values relative to the viewport, but we are positioning the tooltip using position: absolute relative to the document. Without window.scrollY, the tooltip would be mispositioned if the page is scrolled.
For the horizontal position (left), we need to center the tooltip above the trigger. We find the center of the trigger by taking triggerRect.left + triggerRect.width / 2. This gives us the horizontal center point of the link. Then we subtract tooltipRect.width / 2 to offset the tooltip so its center aligns with the trigger's center. We add window.scrollX for the same scroll offset reason as above.
We start the tooltip with opacity: 0 in CSS and set it to opacity: 1 in JavaScript after positioning. This prevents the tooltip from briefly flickering at the wrong position before the calculation runs.
Common Pitfalls
- The most common mistake candidates make is forgetting to account for scroll position.
getBoundingClientRect()returns values relative to the viewport, not the document. If the page is scrolled and you do not addwindow.scrollYandwindow.scrollX, the tooltip will appear in the wrong position. This is a very common source of bugs in production as well. - Another common mistake is trying to calculate the tooltip's dimensions before appending it to the DOM. You must first append the tooltip to the DOM, then call
getBoundingClientRect()on it. An element that is not in the DOM has no dimensions, sogetBoundingClientRect()will return all zeros. - Some candidates try to position the tooltip using
position: fixedinstead ofposition: absolute. Whilefixedavoids the scroll offset issue, it breaks when the trigger element is inside a scrollable container. Usingabsolutewith scroll offsets is the more robust approach.
Interview Tip
When you write the positioning logic, explain the math to the interviewer as you go. Say something like "I am calculating the center of the trigger element and then offsetting the tooltip by half its width to center it." This shows the interviewer that you understand the geometry and are not just guessing values until it looks right. Interviewers pay close attention to whether candidates can reason about solution.
Step 4: Styling the Tooltip
Now let's style the tooltip so it looks polished. Notice that the starter code already provides CSS variables in :root for colors, sizing, and spacing. We will use these variables throughout our CSS so the tooltip is easy to customise later. The variables are already provided in the starter code so you do not need to spend time deciding on colors.
:root {
--tooltip-bg: #fff8c4;
--tooltip-color: #5c4b00;
--tooltip-border: #e8d44d;
--tooltip-arrow-size: 6px;
--tooltip-radius: 6px;
--tooltip-font-size: 14px;
--link-color: #1a73e8;
--page-bg: #e8f4fd;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
system-ui,
-apple-system,
sans-serif;
background-color: var(--page-bg);
}
.wrapper {
display: flex;
justify-content: center;
padding-top: 80px;
}
.trigger {
color: var(--link-color);
font-size: 16px;
text-decoration: underline;
cursor: pointer;
}
.tooltip {
position: absolute;
background-color: var(--tooltip-bg);
color: var(--tooltip-color);
padding: 8px 16px;
border: 1px solid var(--tooltip-border);
border-radius: var(--tooltip-radius);
font-size: var(--tooltip-font-size);
font-weight: 500;
white-space: nowrap;
pointer-events: none;
opacity: 0;
z-index: 1000;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: var(--tooltip-arrow-size);
border-style: solid;
border-color: var(--tooltip-border) transparent
transparent transparent;
}
.tooltip::before {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: calc(var(--tooltip-arrow-size) - 1px);
border-style: solid;
border-color: var(--tooltip-bg) transparent transparent
transparent;
z-index: 1;
}Let's talk about the important CSS decisions.
The tooltip uses position: absolute so we can place it at exact pixel coordinates calculated by our JavaScript. We set opacity: 0 by default and set it to opacity: 1 from JavaScript after positioning, this prevents the flash of the tooltip appearing at position 0, 0 before our JavaScript positions it correctly.
We use pointer-events: none on the tooltip so it does not interfere with mouse events. Without this, when the user moves their mouse from the trigger toward the tooltip, the tooltip could steal the mouseenter event, causing flickering.
The tooltip has a border: 1px solid var(--tooltip-border) which gives it a visible yellow border. The arrow needs to match this bordered look, so we use two pseudo-elements. The ::after pseudo-element creates the outer arrow using the border color, and the ::before pseudo-element creates a slightly smaller inner arrow using the tooltip background color. The inner arrow is 1px smaller (calc(var(--tooltip-arrow-size) - 1px)) and sits on top with z-index: 1, which creates the illusion of a bordered arrow that seamlessly continues the tooltip's border.
Both pseudo-elements are positioned at top: 100% (the bottom edge of the tooltip) and left: 50% with transform: translateX(-50%) to center them horizontally.
white-space: nowrap prevents the tooltip text from wrapping to a new line. For most tooltips, a single line is expected. z-index: 1000 ensures the tooltip appears above other content on the page.
Don't Stress About the Triangle
Even if you are not able to create the triangle arrow, it is completely fine. If your tooltip positioning with JavaScript is working correctly and the basic styling is in place, you will for sure pass the interview. The arrow is a nice finishing touch but it is not what the interviewer is evaluating you on. Focus your energy on getting the positioning logic right first.
The CSS Border Triangle Trick
The border triangle trick works because when an element has zero width and height, the borders meet at points rather than along straight edges. By making three borders transparent and one colored, you get a triangle shape. This is one of the most common CSS tricks and has been used in production for years. It is worth knowing because interviewers often follow up with "how does the arrow work?" and expect you to explain the border trick. In our case, we use two triangles layered on top of each other to create a bordered arrow effect that matches the tooltip's border.
Step 5: Putting It All Together
Here is the complete solution with all the pieces connected.
index.html
<div class="wrapper">
<a
href="javascript:void(0)"
class="trigger"
data-tooltip="Tooltip text!"
>
This is a link
</a>
</div>index.js
import './styles.css';
const TOOLTIP_GAP = 8;
function showTooltip(triggerElement) {
const tooltipText =
triggerElement.getAttribute('data-tooltip');
if (!tooltipText) return;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = tooltipText;
document.body.appendChild(tooltip);
const triggerRect =
triggerElement.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const top =
triggerRect.top -
tooltipRect.height -
TOOLTIP_GAP +
window.scrollY;
const left =
triggerRect.left +
triggerRect.width / 2 -
tooltipRect.width / 2 +
window.scrollX;
tooltip.style.top = `${top}px`;
tooltip.style.left = `${left}px`;
tooltip.style.opacity = '1';
triggerElement._tooltip = tooltip;
}
function hideTooltip(triggerElement) {
const tooltip = triggerElement._tooltip;
if (tooltip) {
tooltip.remove();
triggerElement._tooltip = null;
}
}
const triggers = document.querySelectorAll(
'[data-tooltip]'
);
triggers.forEach((trigger) => {
trigger.addEventListener('mouseenter', () =>
showTooltip(trigger)
);
trigger.addEventListener('mouseleave', () =>
hideTooltip(trigger)
);
});Open the browser and hover over the link. You should see a yellow tooltip appear centered above the link with a bordered arrow pointing down. Move your mouse away and the tooltip should disappear.
Communicate While You Code
Throughout the interview, talk about the important decisions you are making. For example:
- I am using data attributes because it keeps the tooltip reusable and decoupled from the JavaScript logic
- I am using getBoundingClientRect because the interviewer wants precise pixel positioning, not just CSS
- I am adding window.scrollY because getBoundingClientRect gives viewport-relative coordinates
- I am appending to document.body first and then measuring because the tooltip needs to be in the DOM to have dimensions
The interviewer loves candidates who can reason about their code out loud. Do not silently code the solution. Explain the important decisions, especially the positioning math.
Interviewer Criteria
HTML/CSS
Does the tooltip layout look clean and polished?
Did you use semantic and meaningful class names?
Did you use the CSS border trick for the arrow, and can you explain how it works?
Did you use pointer-events: none to prevent the tooltip from interfering with mouse events?
JavaScript
Did you use getBoundingClientRect() for positioning instead of relying on CSS-only solutions?
Can you explain the centering math: trigger center minus half the tooltip width?
Did you account for scroll position by adding window.scrollY and window.scrollX?
Did you append the tooltip to the DOM before measuring its dimensions?
Did you use data attributes to store tooltip text, keeping the solution generic and reusable?
Did you properly clean up the tooltip on mouseleave by removing it from the DOM?
Are variable and function names descriptive and consistent?
Time Checkpoints
- 10:00 AM
Interview starts 👥
- 10:03 AM
Prompt given by the interviewer
- 10:05 AM
Candidate reads the prompt, asks clarifying questions, and starts coding
- 10:10 AM
HTML structure created with trigger link and data-tooltip attribute
- 10:18 AM
Basic tooltip creation and removal on mouseenter/mouseleave working
- 10:30 AM
Tooltip positioned and centered above trigger using getBoundingClientRect
- 10:38 AM
CSS styling completed with arrow pseudo-element
- 10:42 AM
Discussion with interviewer
- 10:45 AM
Interview ends ✅