Skip to main contentSkip to docs navigation

Small text overlays that appear on hover or focus, positioned by Floating UI and themed via CSS custom properties.

@layer components
Requires JavaScript
Depends onFloating UI
Context tokens

Introduction

Tooltips are small informational overlays triggered by hover or focus on an interactive element. The plugin uses Floating UI for positioning and generates the tooltip element on demand, appending it to document.body by default.

Key behaviors:

  • Tooltips initialize automatically on any element with data-cx-toggle="tooltip".
  • Tooltip text comes from the element's native title attribute or from data-cx-title.
  • Tooltips with zero-length content are never shown.
  • Calling show on an element with an inline style.display: none throws an error.
  • Tooltips on .disabled or disabled elements must be attached to a wrapper element.
  • Multi-line inline links center the tooltip. Add white-space: nowrap to <a> elements to prevent line-wrap centering.
  • Hide a tooltip before removing its trigger element from the DOM.
  • Trigger elements inside a shadow DOM are supported.

By default, this component uses the built-in content sanitizer, which strips out any HTML elements that are not explicitly allowed. See the sanitizer section in our JavaScript documentation for more details.

Chassis CSS respects user accessibility preferences by automatically disabling animations when the prefers-reduced-motion media query is detected. See the reduced motion guidelines in our accessibility documentation for implementation details.

Basic structure

Add data-cx-toggle="tooltip" to any keyboard-focusable, interactive element. Provide the tooltip text via the native title attribute (preferred) or data-cx-title. The plugin creates and positions the tooltip on first hover or focus.

HTML
<button type="button" class="button primary"
    data-cx-toggle="tooltip"
    title="Tooltip text">
  Example tooltip
</button>

On initialization, the plugin moves the native title value to data-cx-original-title and removes the attribute — suppressing the browser's built-in tooltip before showing its own.

The plugin generates the following markup and inserts it into the container (document.body by default):

HTML
<div class="tooltip cx-tooltip-auto" role="tooltip">
  <div class="tooltip-arrow"></div>
  <div class="tooltip-inner">Tooltip text</div>
</div>

After each computePosition call, the plugin writes the final resolved placement to data-cx-placement on the tooltip element. The cx-tooltip-auto class reads that attribute and applies the styles of the matching directional class (cx-tooltip-top, cx-tooltip-end, cx-tooltip-bottom, or cx-tooltip-start) to position the arrow correctly. The generated element receives a unique id; the trigger element receives a matching aria-describedby while the tooltip is visible, and the attribute is removed on hide.

Tooltips work on any interactive element, including inline links:

Inline text with a link tooltip and a second link with its own tooltip.

HTML
<p class="fg-3">Inline text with <a href="#" data-cx-toggle="tooltip" title="Default tooltip">a link tooltip</a> and a second link with <a href="#" data-cx-toggle="tooltip" title="Another tooltip">its own tooltip</a>.</p>

Placement

Set data-cx-placement to control which side the tooltip appears on. The logical values start and end resolve to the inline-start and inline-end sides and adapt to the document's text direction. The physical values left, right, top, and bottom always place the tooltip on that side regardless of direction. The default is top.

HTML
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on top"
  data-cx-placement="top">
top
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on bottom"
  data-cx-placement="bottom">
bottom
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on start"
  data-cx-placement="start">
start
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on end"
  data-cx-placement="end">
end
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on left"
  data-cx-placement="left">
left
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Tooltip on right"
  data-cx-placement="right">
right
</button>

When the preferred placement would cause the tooltip to overflow its boundary, Floating UI's flip middleware selects a fallback from ['top', 'right', 'bottom', 'left']. Override the fallback list with the fallbackPlacements option.

Responsive placement

The placement value accepts breakpoint prefixes to change direction at different viewport widths. The syntax is the base placement followed by breakpoint:placement tokens, where breakpoints are small, medium, large, xlarge, and 2xlarge. The plugin re-evaluates placement whenever the viewport crosses a registered breakpoint.

HTML
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Responsive tooltip"
  data-cx-placement="top medium:right large:bottom">
Top → medium:right → large:bottom
</button>
<button type="button" class="button primary"
  data-cx-toggle="tooltip" title="Responsive tooltip"
  data-cx-placement="left large:top">
Left → large:top
</button>

Advanced features

The tooltip plugin supports HTML content, non-text trigger elements, and a wrapper pattern for attaching tooltips to disabled elements.

HTML content

Set data-cx-html="true" to render HTML tags inside the tooltip. HTML content passes through the built-in sanitizer unless a custom sanitizeFn is provided. Use plain text for any content derived from user input.

HTML
<button type="button" class="button primary"
  data-cx-toggle="tooltip"
  title="<em>Tooltip</em> <u>with</u> <b>HTML</b>"
  data-cx-html="true">
Tooltip with HTML
</button>

Non-text trigger elements, such as SVG graphics, require a focusable wrapper. When the wrapper has no text content and no aria-label, the plugin sets aria-label automatically from the tooltip text. An explicit aria-label overrides that automatic value — useful when a more descriptive label than the raw tooltip text is appropriate:

HTML
<a href="#" class="d-inline-block"
  data-cx-toggle="tooltip" title="Default tooltip"
  aria-label="Hover or focus to see default tooltip">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100" aria-hidden="true">
  <rect width="100%" height="100%" fill="#563d7c"/>
  <circle cx="50" cy="50" r="30" fill="#007bff"/>
</svg>
</a>

Disabled elements

Disabled elements do not receive hover or focus events and cannot directly trigger a tooltip. Wrap a disabled element in a <span> or <div>, place the tooltip attributes on the wrapper, and add tabindex="0" to keep the wrapper reachable by keyboard.

HTML
<span class="d-inline-block" tabindex="0"
  data-cx-toggle="tooltip" title="Disabled tooltip">
<button class="button primary" type="button" disabled>Disabled button</button>
</span>

Theming

Override tooltip appearance by assigning a custom class with data-cx-custom-class and redefining the component's custom properties on that class.

.custom-tooltip {
  --cx-tooltip-bg-color: var(--cx-primary-60);
  --cx-tooltip-fg-color: var(--cx-primary-20);
}
HTML
<button type="button" class="button primary"
  data-cx-toggle="tooltip"
  title="This top tooltip is themed via CSS variables."
  data-cx-placement="top"
  data-cx-custom-class="custom-tooltip">
Custom tooltip
</button>

Accessibility

The Tooltip plugin manages aria-describedby automatically: the trigger element receives an aria-describedby attribute pointing at the generated tooltip's id when the tooltip becomes visible, and the attribute is removed on hide. The generated tooltip element carries role="tooltip".

On initialization, the plugin removes the native title attribute from the trigger element and moves its value to data-cx-original-title, suppressing the browser's built-in tooltip. If the trigger has no visible text content and no aria-label, the plugin sets aria-label to the tooltip text.

The default trigger is 'hover focus', so tooltips respond to both pointer and keyboard focus without additional configuration. Add tooltips only to natively interactive elements — links, buttons, and form controls — that already receive keyboard focus. Elements made focusable with tabindex="0" can create confusing tab stops and may not be announced consistently by screen readers.

A trigger value of 'hover' alone makes the tooltip unreachable by keyboard. Always include focus in the trigger list when the tooltip conveys information not available elsewhere on the page.

JavaScript API

Import the Tooltip class for programmatic access. Floating UI must be available, either via the bundled distribution or an import map:

JavaScript
import { Tooltip } from '@chassis-ui/css'

Triggers

The data-attribute API activates tooltips without writing JavaScript:

AttributeDescription
data-cx-toggle="tooltip"Initializes the tooltip on the element.
data-cx-titleTooltip text. Alternative to the native title attribute.
data-cx-placementPreferred placement. Logical: start, end (adapt to text direction). Physical: top, bottom, left, right. Default: top. Accepts responsive prefixes.
data-cx-triggerEvents that show and hide the tooltip: click, hover, focus, manual. Default: hover focus.
data-cx-containerCSS selector or DOM element to append the generated tooltip to. Default resolves to document.body.
data-cx-delayShow and hide delay in milliseconds. A number applies to both; an object sets them independently: {"show":500,"hide":100}.
data-cx-custom-classExtra class names added to the tooltip element for theming.
data-cx-htmlSet to true to render HTML in the tooltip content.

For security reasons, sanitize, sanitizeFn, and allowList cannot be set via data-cx-* attributes or data-cx-config. Use JavaScript constructor options instead.

Initialization

Instantiate a Tooltip by passing an element reference and an optional options object. The constructor registers hover and focus listeners immediately:

JavaScript
import { Tooltip } from '@chassis-ui/css'

const tooltip = new Tooltip(document.getElementById('myTrigger'), {
  placement: 'bottom',
  delay: { show: 200, hide: 100 }
})

Tooltips reposition automatically when a parent has overflow: auto or overflow: scroll. To constrain the shift boundary, pass an HTMLElement to the boundary option:

JavaScript
const tooltip = new Tooltip('#example', {
  boundary: document.body
})

Methods

The following methods are available on each Tooltip instance:

All Chassis CSS component methods are asynchronous and initiate CSS transitions. Methods return immediately when the transition begins, not when it completes. Calling methods on components that are already transitioning will be ignored to prevent conflicts. Learn more about Chassis JavaScript patterns.

MethodDescription
disablePrevents the tooltip from appearing until re-enabled.
disposeHides and destroys the instance, removing data stored on the trigger element. Tooltips created with a selector option cannot be individually destroyed on descendant triggers.
enableRestores the tooltip after disable. Tooltips are enabled by default.
getInstanceStatic. Returns the existing Tooltip instance associated with a DOM element, or null.
getOrCreateInstanceStatic. Returns the existing Tooltip instance, or creates and returns a new one.
hideHides the tooltip. Returns before the hidden.cx.tooltip event fires.
setContentUpdates the tooltip content after initialization.
showShows the tooltip. Returns before the shown.cx.tooltip event fires. Tooltips with empty content are not shown.
toggleToggles the tooltip. Returns before the show or hide event fires.
toggleEnabledToggles between enabled and disabled states.
updateRecalculates the tooltip's position.

setContent accepts an object keyed by selector within the tooltip template. The recognized key is .tooltip-inner. Each value may be a string, Element, function, or null:

JavaScript
import { Tooltip } from '@chassis-ui/css'

const tooltip = Tooltip.getInstance('#myTrigger')
tooltip.setContent({ '.tooltip-inner': 'Updated text' })

Options

Options are set via data-cx-* attributes or passed to the constructor as an object. Attribute names use the kebab-case form of the option name — data-cx-custom-class, not data-cx-customClass. Attribute values are parsed to their native types:"true"true, "0"0, and valid JSON strings to objects.

Use data-cx-config to pass multiple options as a JSON string:data-cx-config='{"delay":200}'. Individual data-cx-* attributes take precedence over data-cx-config. You can also use JSON values in individual attributes, such as data-cx-delay='{"show":100,"hide":200}'.

When initializing components, Chassis merges configurations from multiple sources in this priority order: default settings, data-cx-config values, individual data-cx-*attributes, and finally any JavaScript object options. Values defined later in this sequence override earlier ones.

NameTypeDefaultDescription
allowListobjectDefault valueAllowed tags and attributes for HTML content. Tags and attributes absent from this list are stripped by the content sanitizer.
Exercise caution when extending this list. See OWASP's XSS Prevention Cheat Sheet.
animationbooleantrueApplies a CSS fade transition.
boundarystring, element'clippingParents'Overflow constraint boundary for Floating UI's shift middleware. The default string 'clippingParents' is translated to 'clippingAncestors' internally. Pass an HTMLElement via JavaScript to constrain to a specific element. See Floating UI shift docs.
containerstring, element, falsefalseElement to append the tooltip to. The value false resolves to document.body at runtime. Set explicitly to keep the tooltip in a specific stacking context.
customClassstring, function''Additional class names applied to the tooltip on show. Pass a space-separated string for multiple classes, or a function that returns a string.
delaynumber, object0Show and hide delay in milliseconds. A number applies to both; an object sets them independently: { "show": 500, "hide": 100 }.
fallbackPlacementsarray['top', 'right', 'bottom', 'left']Ordered fallback placements for Floating UI's flip middleware. Must use physical values (top, right, bottom, left); start and end are not resolved here. See Floating UI flip docs.
floatingConfignull, object, functionnullOverrides the default Floating UI computePosition configuration. A function receives the default config object and must return a new config. See Floating UI computePosition docs.
htmlbooleanfalseRenders HTML tags in the tooltip content. When false, content is inserted via innerText. Use false for user-generated content to prevent XSS.
offsetarray, string, function[0, 6]Distance between the tooltip and its trigger [crossAxis, mainAxis]. Pass a comma-separated string in data attributes: data-cx-offset="10,20". A function receives placement and rects and must return a two-number array. See Floating UI offset docs.
placementstring, function'top'Preferred placement. Logical values start and end adapt to text direction. Physical values top, bottom, left, and right always place the tooltip on that side. Accepts responsive prefixes ('start medium:end'). A function receives the tooltip element and the trigger element and must return a placement string.
sanitizebooleantrueEnables content sanitization for template, content, and title.
Disabling sanitization removes XSS protection. See OWASP's XSS Prevention Cheat Sheet. Vulnerabilities arising solely from disabled sanitization are outside Chassis CSS's security scope.
sanitizeFnnull, functionnullCustom sanitization function. Replaces the built-in sanitizer when provided.
selectorstring, falsefalseCSS selector for event delegation. Enables attaching tooltips to dynamically added descendants matching the selector.
templatestring'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'Base HTML for the generated tooltip element. Content is injected into .tooltip-inner; .tooltip-arrow receives position styling. The outermost element must carry .tooltip and role="tooltip".
titlestring, element, function''Content for .tooltip-inner. A function is called with the trigger element as its argument.
triggerstring'hover focus'Events that show and hide the tooltip: click, hover, focus, manual. Pass multiple values separated by spaces. manual cannot be combined with other triggers and requires calling show, hide, or toggle programmatically.

floatingConfig function

When floatingConfig is a function, it receives the default Floating UI configuration object and must return a new config:

JavaScript
const tooltip = new Tooltip(element, {
  floatingConfig(defaultConfig) {
    return { ...defaultConfig, strategy: 'fixed' }
  }
})

Events

Each lifecycle transition fires a namespaced event on the trigger element:

JavaScript
import { Tooltip } from '@chassis-ui/css'

const trigger = document.getElementById('myTrigger')

trigger.addEventListener('hidden.cx.tooltip', () => {
  Tooltip.getInstance(trigger).dispose()
})
EventDescription
hide.cx.tooltipFires immediately when hide is called.
hidden.cx.tooltipFires after the tooltip is fully hidden, once CSS transitions complete.
inserted.cx.tooltipFires after show.cx.tooltip when the tooltip element has been added to the DOM.
show.cx.tooltipFires immediately when show is called.
shown.cx.tooltipFires after the tooltip is fully visible, once CSS transitions complete.

CSS

The Tooltip component can be customized at both runtime (via custom properties) and compile time (via Sass variables).

Custom properties

The Tooltip component exposes CSS custom properties to control its appearance at runtime.

--zindex: var(--tooltip-zindex, #{$zindex-tooltip});
--max-width: var(--tooltip-max-width, #{$tooltip-max-width});
--padding-x: var(--tooltip-padding-x, #{$tooltip-padding-x});
--padding-y: var(--tooltip-padding-y, #{$tooltip-padding-y});
--margin: var(--tooltip-margin, #{$tooltip-margin});
@include map-font($tooltip-font, tooltip);
--fg-color: var(--tooltip-fg-color, #{$tooltip-fg-color});
--bg-color: var(--tooltip-bg-color, #{$tooltip-bg-color});
--border-radius: var(--tooltip-border-radius, #{$tooltip-border-radius});
--opacity: var(--tooltip-opacity, #{$tooltip-opacity});
--arrow-width: var(--tooltip-arrow-width, #{$tooltip-arrow-width});
--arrow-height: var(--tooltip-arrow-height, #{$tooltip-arrow-height});

Sass variables

The Tooltip component uses Sass variables in scss/config/_defaults.scss to define its defaults.

$tooltip-font:                      $font-text-small-normal;
$tooltip-max-width:                 12.5rem; //200px;
$tooltip-fg-color:                  var(--fg-inverse);
$tooltip-bg-color:                  var(--bg-inverse);
$tooltip-border-radius:             var(--border-radius-medium);
$tooltip-opacity:                   .9;
$tooltip-padding-y:                 $spacer * .25;
$tooltip-padding-x:                 $spacer * .5;
$tooltip-margin:                    null;

$tooltip-arrow-width:               .8rem;
$tooltip-arrow-height:              .4rem;