Tooltip
CSS-only tooltip pulled from [data-tooltip="…"]. No JS, no
extra markup: the bubble is a ::after pseudo-element whose
text is read via content: attr(data-tooltip). Revealed on
:hover and :focus-within so keyboard users get
the same affordance as pointer users. No !important on the
trigger's position rule — see the accessibility section for
why.
How it works
-
The trigger declares
position: relativeso the absolutely positioned::afteranchors to it. The rule is not marked!important: if a consumer has already positioned the trigger (e.g. an absolutely placed icon), stomping on that author intent would silently break layouts. When the trigger isposition: static, the bubble anchors to the nearest positioned ancestor instead. -
The bubble's text comes from
content: attr(data-tooltip). When the attribute is empty (data-tooltip=""), the bubble is suppressed via:not([data-tooltip=""])— a useful toggle pattern (blank the attribute to hide without removing the hook). -
Default position is above the trigger, horizontally centred. Three alternative positions are available via the
data-tooltip-positionattribute:Position Attribute top (default) bottom data-tooltip-position="bottom"left data-tooltip-position="left"right data-tooltip-position="right"The helper is keyed off
[data-tooltip]already (the bubble text comes fromattr(data-tooltip)), so keeping position on the same axis keeps the API single-shape. Earlier releases shipped class hooks (.tooltip-bottom,.tooltip-left,.tooltip-right) — those have been removed; migrate to the attribute form. -
z-indexdefaults to 1080 to sit above Bootstrap-style modals (1050) and popovers (1070) when frameworks are mixed. -
The bubble uses
pointer-events: noneso it never intercepts clicks meant for content underneath.
Live
Four positions — hover or focus to reveal
<div style="display: flex; gap: 1.5rem; justify-content: center; padding: 3rem 1rem;">
<button class="button" data-tooltip="Default (top)">Top</button>
<button class="button" data-tooltip="Below the trigger" data-tooltip-position="bottom">Bottom</button>
<button class="button" data-tooltip="To the left" data-tooltip-position="left">Left</button>
<button class="button" data-tooltip="To the right" data-tooltip-position="right">Right</button>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'helper-tooltip', 'element-button')
); Markup
<!-- Default position: top -->
<button class="button" data-tooltip="Save the current draft">Save</button>
<!-- Below the trigger -->
<button class="button" data-tooltip="Permanently delete" data-tooltip-position="bottom">Delete</button>
<!-- To the left -->
<button class="button" data-tooltip="Previous step" data-tooltip-position="left">Back</button>
<!-- Hide the tooltip without removing the hook: blank the attribute -->
<button class="button" data-tooltip="">No tooltip yet</button> Accessibility
- Activated on both
:hoverand:focus-within, so keyboard users reach the tooltip by tabbing to the trigger (or any focusable descendant). Pointer users get the same content on hover. - The
prefers-reduced-motion: reducemedia query disables the opacity transition so the bubble appears instantly for users who opt out of motion. - The bubble is rendered via
::afterandcontent: attr(...), which means assistive technologies that read pseudo-element content will announce it. If you need guaranteed screen-reader exposure regardless of AT pseudo-content support, also mirror the value intoaria-describedbyoraria-labelon the trigger. content: attr(...)forstringvalues is supported across every evergreen browser in scope (per.browserslistrc), so no fallback is required. The wider CSS Values 5attr()type-aware syntax is not used here.- The default
position: relativeon the trigger is intentionally not!important— a tooltip that silently broke an author's positioning would be worse than a tooltip that anchors to an ancestor. - For a tooltip that flips, shifts, or stays in the viewport on edge cases, you need a JS-positioned library such as Floating UI (the spiritual successor to Popper). The CSS-only helper is intentionally scoped to the static four-position case.
Variables
| Variable | Default | Notes |
|---|---|---|
$selector | '[data-tooltip]' | The selector the rule keys off. Rename to [data-hint] etc. if it conflicts with another library. |
$bg | rgba(0, 0, 0, 0.85) | Bubble background fallback. Themes ship --kc-tooltip-bg to override per-theme. |
$fg | #fff | Bubble text-color fallback. Themes ship --kc-tooltip-fg to override per-theme. |
$position-bottom-selector | '[data-tooltip-position="bottom"]' | Selector that flips the bubble below the trigger. |
$position-left-selector | '[data-tooltip-position="left"]' | Selector that anchors the bubble to the inline-start edge. |
$position-right-selector | '[data-tooltip-position="right"]' | Selector that anchors the bubble to the inline-end edge. |
$padding | 0.4em 0.8em | Bubble inner padding. em-based so it scales with $font-size. |
$border-radius | 4px | Bubble corner radius. |
$font-size | 0.8em | Bubble text size, relative to the trigger. |
$offset | 0.5rem | Distance from the bubble to the trigger edge. |
$z-index | 1080 | Matches Bootstrap's tooltip layer; sits above modals (1050) and popovers (1070). |
$transition-duration | 120ms | Fade-in / fade-out duration. Suppressed under prefers-reduced-motion: reduce. |
Override example
@use '@adnap/krysalicss/helper/tooltip' with (
$selector: '[data-tooltip]',
$bg: rgba(20, 20, 24, 0.92),
$fg: #fff,
$padding: 0.5em 0.9em,
$border-radius: 6px,
$font-size: 0.85em,
$offset: 0.6rem,
$z-index: 1080,
$transition-duration: 120ms,
);
/* Theme-switchable: ship per-theme tokens that the bubble picks up. */
:root {
--kc-tooltip-bg: rgba(20, 20, 24, 0.92);
--kc-tooltip-fg: #fff;
}
:root[data-theme="dark"] {
--kc-tooltip-bg: rgba(245, 245, 250, 0.95);
--kc-tooltip-fg: #111;
} Tokens consumed
| Token | Used for |
|---|---|
--kc-tooltip-bg | Bubble background. Falls back to $bg when undefined. |
--kc-tooltip-fg | Bubble text color. Falls back to $fg when undefined. |
Sizes and timings (padding, radius, offset, duration) are baked at SCSS-compile time — use the SCSS override above to change them.
Forced-colors fallback
Under forced-colors: active (Windows high-contrast and similar OS
settings), the bubble switches to system Canvas / CanvasText
colours with a 1 px CanvasText border. This keeps the tooltip
legible against any user-imposed palette regardless of the framework's
default ink-on-black bubble.