Tooltip ​

Label overlay for a control: role="tooltip", default placement "top", and delayed open/close on the DOM controller / sometic-tooltip (pointer + focus). React and Vue adapters render a labelled tip when open is true without delay timers or positioning controllers (resolve-only shells; see Beta maturity).

PreviewHover or focus the control

Usage ​

tsx
import { useState } from "react";
import { Tooltip } from "@sometic/react/overlay";

export function Example() {
    const [open, setOpen] = useState(false);
    return (
        <Tooltip open={open} label="Save (Ctrl+S)">
            <button
                type="button"
                onMouseEnter={() => setOpen(true)}
                onMouseLeave={() => setOpen(false)}
                onFocus={() => setOpen(true)}
                onBlur={() => setOpen(false)}
            >
                Save
            </button>
        </Tooltip>
    );
}
vue
<script setup>
import { ref } from "vue";
import { Tooltip } from "@sometic/vue/overlay";

const open = ref(false);
</script>

<template>
    <Tooltip :open="open" label="Save (Ctrl+S)">
        <button
            type="button"
            @mouseenter="open = true"
            @mouseleave="open = false"
            @focus="open = true"
            @blur="open = false"
        >
            Save
        </button>
    </Tooltip>
</template>
js
import { createTooltipController, resolveTooltip } from "@sometic/dom/tooltip";

const tip = document.querySelector("#tooltip");
const controller = createTooltipController({
    defaultOpen: false,
    getContent: () => tip,
    onOpenChange(next) {
        const view = resolveTooltip({ open: next });
        tip.hidden = !next;
        for (const [key, attr] of Object.entries(view.attributes)) {
            tip.setAttribute(key, attr);
        }
    },
});

const trigger = document.querySelector("#save");
trigger.addEventListener("mouseenter", () => controller.setOpen(true));
trigger.addEventListener("mouseleave", () => controller.setOpen(false));
trigger.addEventListener("focus", () => controller.setOpen(true));
trigger.addEventListener("blur", () => controller.setOpen(false));
html
<script type="module">
    import { registerOverlayElements } from "@sometic/elements/overlay";
    registerOverlayElements();
</script>

<sometic-tooltip placement="top">
    <button type="button">Save</button>
    <div data-slot="content">Save (Ctrl+S)</div>
</sometic-tooltip>
html
<script src="https://cdn.jsdelivr.net/npm/@sometic/elements@1.1.6/dist/cdn/sometic-elements.iife.js"></script>

<sometic-tooltip placement="top">
    <button type="button">Save</button>
    <div data-slot="content">Save (Ctrl+S)</div>
</sometic-tooltip>
html
<script
    type="module"
    src="https://cdn.jsdelivr.net/npm/@sometic/elements@1.1.6/dist/cdn/sometic-elements.esm.js"
></script>

<sometic-tooltip placement="top">
    <button type="button">Save</button>
    <div data-slot="content">Save (Ctrl+S)</div>
</sometic-tooltip>

Vue ​

vue
<script setup>
import { ref } from "vue";
import { Tooltip } from "@sometic/vue/overlay";

const open = ref(false);
</script>

<template>
    <Tooltip :open="open" label="Save (Ctrl+S)">
        <button
            type="button"
            @mouseenter="open = true"
            @mouseleave="open = false"
            @focus="open = true"
            @blur="open = false"
        >
            Save
        </button>
    </Tooltip>
</template>

Beta honesty: React/Vue own hover/focus delays and positioning unless you use createTooltipController or sometic-tooltip. Dialog is the overlay that ships controller-wired React/Vue adapters in this beta.

How it works ​

  1. Resolve (resolveTooltip): role="tooltip", data-slot="root", data-state, data-placement (default "top"), absolute style hooks.
  2. Controller (createTooltipController): controllable open, openDelayMs / closeDelayMs, scheduleOpen / scheduleClose, positioning against trigger/content.
  3. Adapters: React/Vue wrap children in a relative span and render a tip div when open; you drive open state and delays on the shell path.
  4. Custom element: sometic-tooltip observes open, placement, shadow and owns controller behavior.

Anatomy ​

PartRole
TriggerDefault slot / children; must keep its own accessible name
Tiplabel text in a role="tooltip" element when open

Resolve attrs on tip: data-state, data-placement.

Props / attributes ​

React TooltipProps ​

PropTypeDefaultDescription
openbooleanfalseShow tip
labelstringrequiredTip text
childrenReactNoderequiredTrigger
Native div attrsApplied to tip when open

Controller extras when used directly: placement, offset, openDelayMs, closeDelayMs, onOpenChange.

Vue ​

Props: open, label (required). Default slot is the trigger.

Custom element (sometic-tooltip) ​

Observed: open, placement, shadow. Prefer CE/controller for delay + position.

Events / callbacks ​

SurfaceEvent
React / VueControl open externally
CE / controlleronOpenChange / open-change

Controlled vs uncontrolled ​

Controller supports both. React/Vue shells are controlled via open.

Form participation ​

N/A.

Accessibility ​

ConcernGuidance
RoleTip uses role="tooltip"
Trigger nameTrigger must have its own accessible name; tip is supplementary
Keyboard / focusPrefer focus + hover open on CE/controller; Space/Enter activate buttons
Essential textDo not put required instructions only in a tooltip
ARIA associationWire aria-describedby to the tip id when you need a stronger link
EscapeFollows dismissable behavior on controller where wired

Styling ​

[role="tooltip"], [data-state], [data-placement] on CE/controller resolves. Unstyled by default.

Edge cases ​

  • Empty label: invalid usage (label is required).
  • Touch devices: hover delays differ; test CE/controller paths.
  • SSR: no import-time DOM; create controllers / register CE in the browser.
  • Shell without timers: rapid hover flicker is your responsibility on React/Vue.
  • Popover content: interactive panels belong in Popover, not Tooltip.
  • Multi-instance: independent resolves/controllers; no shared singleton.

Performance notes ​

Timers only on the controller path; cancel on dispose. React/Vue shells are trivial conditional renders. Prefer one shared delay policy via CE/controller rather than per-call ad hoc timers.

When to use / When not ​

Use for short hints on controls (shortcuts, icon meanings).

Do not use for:

FAQ ​

Delays in React/Vue? Not built-in. Use createTooltipController / CE or your own timers.

Why thinner than Dialog? Dialog adapters wire createDialogController. Tooltip React/Vue stay resolve-only shells in this beta.

Keyboard? Prefer focus-driven open via controller/CE so keyboard users see the tip.

ARIA relationship? Optional aria-describedby pointing at the tip id.

Placement? Positioning placements; resolve default is top.

Can tip hold buttons? No. Keep tips non-interactive; use Popover for interactive content.

Empty label? Do not. label is required for a meaningful tip.

Menu? Not this component. See Menu.

Bundle tip? Import @sometic/react/overlay (or Vue / elements / dom subpaths).