How to Design a Tooltip with CSS Only: Patterns, Positioning, and Accessibility

by | Aug 12, 2026 | Uncategorized | 0 comments

Tooltips are one of those tiny UI elements that can make or break the user experience. When done right, they guide users, clarify actions, and add polish. When done wrong, they block content, disappear too fast, or become invisible to keyboard and screen reader users. In this tutorial, we’ll show you how to build a CSS tooltip from scratch using only pseudo-elements and data-* attributes, without a single line of JavaScript.

By the end of this guide, you’ll have a reusable tooltip system that supports four positions (top, right, bottom, left), works on hover and keyboard focus, and respects accessibility standards.

Why Build a CSS-Only Tooltip?

Frameworks like Bootstrap, Tailwind, and Flowbite offer tooltip components, but they often ship with JavaScript overhead you may not need. A pure CSS tooltip has real advantages:

  • Zero JavaScript: smaller bundle, faster load, no runtime cost.
  • Portable: drop it into any project, any framework.
  • Maintainable: styling and content stay in one place.
  • Accessible when built correctly: works with keyboard focus and screen readers.
tooltip interface design

The Core Concept: Pseudo-Elements + Data Attributes

The trick is simple. We store the tooltip text inside a data-tooltip attribute on any HTML element, then use CSS pseudo-elements (::before and ::after) to render the bubble and the arrow. The content property in CSS can read directly from the attribute using attr().

The HTML

<button class="tooltip" data-tooltip="Save your work" aria-label="Save your work">
  Save
</button>

Notice the aria-label duplicating the tooltip text. This ensures screen readers announce the hint even though CSS pseudo-elements are not always reliably exposed to assistive tech. We’ll cover this in more detail in the accessibility section.

The Base CSS

.tooltip {
  position: relative;
  cursor: pointer;
}

.tooltip::before,
.tooltip::after {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.tooltip::before {
  content: attr(data-tooltip);
  background: #1f2937;
  color: #fff;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 13px;
  white-space: nowrap;
  z-index: 10;
}

.tooltip::after {
  content: "";
  border: 6px solid transparent;
  z-index: 10;
}

.tooltip:hover::before,
.tooltip:hover::after,
.tooltip:focus-visible::before,
.tooltip:focus-visible::after {
  opacity: 1;
}

This gives us the foundation. Now we need to handle positioning.

Positioning Variations: Top, Bottom, Left, Right

We’ll use a second data attribute, data-position, to control where the tooltip appears.

Top Position (default)

.tooltip[data-position="top"]::before,
.tooltip:not([data-position])::before {
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
}

.tooltip[data-position="top"]::after,
.tooltip:not([data-position])::after {
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  border-top-color: #1f2937;
}

Bottom Position

.tooltip[data-position="bottom"]::before {
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
}

.tooltip[data-position="bottom"]::after {
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border-bottom-color: #1f2937;
}

Right Position

.tooltip[data-position="right"]::before {
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
}

.tooltip[data-position="right"]::after {
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  border-right-color: #1f2937;
}

Left Position

.tooltip[data-position="left"]::before {
  right: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
}

.tooltip[data-position="left"]::after {
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  border-left-color: #1f2937;
}

Usage Example

<button class="tooltip" data-tooltip="Delete item" data-position="bottom" aria-label="Delete item">
  Delete
</button>
tooltip interface design

Adding Smooth Entrance Animation

A subtle slide-and-fade makes the tooltip feel less abrupt. Update the hover and focus rules:

.tooltip[data-position="top"]:hover::before,
.tooltip[data-position="top"]:focus-visible::before {
  transform: translateX(-50%) translateY(-4px);
}

.tooltip[data-position="bottom"]:hover::before,
.tooltip[data-position="bottom"]:focus-visible::before {
  transform: translateX(-50%) translateY(4px);
}

Accessibility: The Part Most Tutorials Skip

This is where our tutorial goes further than what you’ll find on most tooltip guides. A tooltip that only works on mouse hover leaves out keyboard users, touch users, and people using screen readers. Here’s how to fix that.

1. Support Keyboard Focus

We already added :focus-visible alongside :hover. This means when a user tabs to the element with a keyboard, the tooltip appears. Always test this by pressing Tab through your interface.

2. Make Content Reachable to Screen Readers

CSS pseudo-element content is not consistently announced by all screen readers. To be safe, mirror the tooltip text in an accessible attribute:

  • For interactive elements (buttons, links): use aria-label or aria-describedby.
  • For non-interactive elements that need explanation: wrap them in a focusable element or use aria-describedby pointing to a visually hidden element.
<button class="tooltip" data-tooltip="Archive this email" aria-describedby="tip1">
  Archive
</button>
<span id="tip1" class="sr-only">Archive this email</span>

<style>
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
</style>

3. Respect prefers-reduced-motion

@media (prefers-reduced-motion: reduce) {
  .tooltip::before,
  .tooltip::after {
    transition: none;
  }
}

4. Ensure Sufficient Contrast

Our dark gray background (#1f2937) against white text passes WCAG AA contrast easily. If you change colors, verify contrast with a tool like WebAIM’s contrast checker.

5. Do Not Rely on Tooltips for Critical Info

Tooltips should enhance, never replace, visible information. If a control cannot be understood without its tooltip, redesign the control itself.

Comparison: CSS Tooltip vs JavaScript Tooltip

Feature CSS-Only JavaScript-Based
Bundle size Zero 5 to 30 KB typical
Collision detection No Yes (auto-flip)
Rich HTML content Text only Any HTML
Performance Excellent Good
Accessibility setup Manual (ARIA) Often built-in
tooltip interface design

When to Use CSS Tooltips (and When Not To)

Great for:

  1. Icon buttons that need a label (search, close, edit, delete).
  2. Form field hints and validation clues.
  3. Abbreviations or acronyms.
  4. Static dashboards and marketing sites.

Not ideal for:

  1. Tooltips near viewport edges (no auto-flipping).
  2. Tooltips containing links, buttons, or interactive content.
  3. Tooltips with dynamic content loaded at runtime.
  4. Complex UI libraries where consistency matters more than bytes.

Full Copy-Paste Snippet

Here’s the complete, production-ready CSS in one block:

.tooltip { position: relative; }
.tooltip::before,
.tooltip::after {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 10;
}
.tooltip::before {
  content: attr(data-tooltip);
  background: #1f2937;
  color: #fff;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 13px;
  white-space: nowrap;
}
.tooltip::after { content: ""; border: 6px solid transparent; }

.tooltip:not([data-position])::before,
.tooltip[data-position="top"]::before {
  bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
}
.tooltip:not([data-position])::after,
.tooltip[data-position="top"]::after {
  bottom: 100%; left: 50%; transform: translateX(-50%); border-top-color: #1f2937;
}

.tooltip[data-position="bottom"]::before {
  top: calc(100% + 10px); left: 50%; transform: translateX(-50%);
}
.tooltip[data-position="bottom"]::after {
  top: 100%; left: 50%; transform: translateX(-50%); border-bottom-color: #1f2937;
}

.tooltip[data-position="right"]::before {
  left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
}
.tooltip[data-position="right"]::after {
  left: 100%; top: 50%; transform: translateY(-50%); border-right-color: #1f2937;
}

.tooltip[data-position="left"]::before {
  right: calc(100% + 10px); top: 50%; transform: translateY(-50%);
}
.tooltip[data-position="left"]::after {
  right: 100%; top: 50%; transform: translateY(-50%); border-left-color: #1f2937;
}

.tooltip:hover::before,
.tooltip:hover::after,
.tooltip:focus-visible::before,
.tooltip:focus-visible::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .tooltip::before, .tooltip::after { transition: none; }
}

Frequently Asked Questions

Can a CSS tooltip contain HTML like links or images?

No. The content: attr() function only accepts plain text. If you need rich content, you’ll need a JavaScript-based solution or a hidden sibling element toggled with CSS.

Why doesn’t my tooltip appear on mobile?

Touch devices don’t have a true hover state. Tapping an element may trigger :hover once, but it’s inconsistent. For mobile-friendly hints, use :focus on buttons or consider a click-to-toggle pattern with a hidden checkbox.

How do I prevent the tooltip from being cut off at screen edges?

Pure CSS can’t detect viewport collisions. You have three options: (1) manually set data-position based on layout, (2) increase padding around edge elements, or (3) switch to a JavaScript library like Floating UI for auto-flipping.

Is title attribute the same as a CSS tooltip?

The native title attribute shows a browser tooltip, but it’s styling is not customizable, its timing is unpredictable, and it’s poorly accessible. A custom CSS tooltip gives you full control over appearance and behavior.

Does this work in all modern browsers?

Yes. All techniques used here (pseudo-elements, attr(), :focus-visible, attribute selectors) are supported in every modern browser as of 2026, including Chrome, Firefox, Safari, and Edge.

Wrapping Up

A well-crafted CSS tooltip proves that you don’t always need JavaScript to build polished, interactive UI. With just pseudo-elements, a data attribute, and a few lines of ARIA, you can deliver a tooltip that’s fast, accessible, and easy to maintain.

At Fat Cow Web Design, we build interfaces that respect performance budgets and accessibility standards from day one. If you’d like our team to audit or upgrade your website’s UI components, get in touch through our contact page.

Search Keywords

Recent Posts

Subscribe Now!