Skip to content

Tooltips (SusKit)

Service: TooltipService (sus-kit/Runtime/Services/) Extension: TooltipExtensions.WithTooltip() (sus-kit/Runtime/Extensions/) Model: SusTooltipCardEntry (sus-kit/Runtime/Models/) Parent plan: LAYER-TOOLTIP.md (in package repo)


1. Purpose

Tooltips appear on hover over a UI element. They render through the shared portal OverlayHost (category Tooltip = 30) and are not clipped by the parent.

Two modes (plus custom cards):

  • Text (ShowAtElement) is a simple string, positioned relative to target VisualElement.
  • Rich (ShowRich) — card with title, description, icon (SusTooltipCardEntry).
  • Custom (ShowContent) — any VisualElement built lazily via Func<​VisualElement>, positioned like text tooltips (auto-flip + clamp). Use when the card is not SusTooltip / SusTooltipCard (e.g. game-specific item chrome). Also exposed as WithTooltip(Func<​VisualElement>, placement).

Show delay (500ms) and hide delay (200ms) are adjustable via ShowDelayMs / HideDelayMs. Only one tooltip is active at a time — call again cancels the previous one.


2. Architecture

Current implementation: TooltipService uses OverlayHost directly for rich tooltips. Component tooltips (SusTooltip.sharq) use SusOverlayManager.ShowTooltip() — a centralized wrapper over OverlayHost with copying styles (CopyAncestorStyleSheets).

OverlayHost (portal, auto-BringToFront via GeometryChangedEvent)
├── [World = 0]       healthbar / nameplate
├── [Transition = 10] curtain
├── [Modal = 20] dialogues
├── [Tooltip = 30] ← TOOLTIPS HERE (SusOverlayManager.ShowTooltip)
├── [Dropdown = 40]   ← Select/Dropdown (SusOverlayManager.Show)
├── [Toast = 45]      snackbars
└── [Console = 50] dev-console
  • Copying styles: OverlayHost.AddToOverlay() causes CopyAncestorStyleSheets() — copies .g.uss With SusComponent-ancestor to RemoveFromHierarchy(). This ensures that the tooltip card retains its styles after teleport.
  • Important: ShowTooltip() DOESN'T RemoveFromHierarchy() before AddToOverlay() — otherwise the parent chain breaks and the styles are not copied.
  • design-tokens.uss And suskit-base.uss loaded onto itself OverlayHost — CSS variables and base styles are visible to all children of the overlay.

3. TooltipService API

File: sus-kit/Runtime/Services/TooltipService.cs

Initialization

csharp
var tooltipService = new TooltipService();
tooltipService.Init(overlayHost);            // OverlayHost for portal rendering
TooltipService.Instance = tooltipService;     // Singleton for WithTooltip

// Factories (Sharq components live in the CompiledUI assembly and are not directly accessible)
tooltipService.CreateTooltip = () => new SusTooltip();
tooltipService.CreateCard = () => new SusTooltipCard();

Methods

MethodSignatureDestination
ShowAtElement(VisualElement target, string text, TooltipPlacement placement = Auto)Text tooltip on an element. Delay: ShowDelayMs (default 500ms). Internally uses ShowContent + CreateTooltip.
ShowContent(VisualElement target, Func<​VisualElement> contentFactory, TooltipPlacement placement = Auto)Custom card on OverlayHost Tooltip layer. Lazy factory after show delay; same placement / auto-flip as text.
ShowRich(SusTooltipCardEntry entry, Vector2? screenPosition = null)Rich card via CreateCard + Apply(entry). If position is omitted — centre of screen.
Hide()Hide immediately.
HideAfterDelay()Hide via HideDelayMs (default 200ms). Used in PointerLeave.

Properties

PropertyTypeDefaultDestination
Instance TooltipService (static)Singleton for WithTooltip
ShowDelayMs Prop<​float>500Delay before showing (ms)
HideDelayMs Prop<​float>200Delay before hiding (ms)
Overlay OverlayHostPortal container
CreateTooltip Func<​VisualElement>Factory SusTooltip (Sharq)
CreateCard Func<​VisualElement>Factory SusTooltipCard (Sharq)
IsVisible bool (read-only)Is the tooltip active

4. Positioning and auto-flip

TooltipPlacement

csharp
public enum TooltipPlacement
{
    Auto,    // Tries from bottom → top → right → left
    Top,
    Bottom,
    Left,
    Right
}

Auto algorithm

  1. Bottom: if target.bottom + tooltip.height + 4px ≤ panel height → Bottom.
  2. Top: if target.top - tooltip.height - 4px ≥ 0 → Top.
  3. Right: if target.right + tooltip.width + 4px ≤ panel width → Right.
  4. Left: fallback.

Clamping

After calculating the position, the tooltip is pressed to the edges of the panel (4px margin):

csharp
x = Mathf.Clamp(x, 4f, panelWidth - tooltipWidth - 4f);
y = Mathf.Clamp(y, 4f, panelHeight - tooltipHeight - 4f);

Pure functions for tests

csharp
// Allow placement (no panel)
TooltipPlacement resolved = TooltipService.ResolvePlacement(
    targetRect, tooltipSize, panelSize, TooltipPlacement.Auto);

// Calculate position (in panel coordinates)
Vector2 pos = TooltipService.ResolvePosition(
    targetRect, tooltipSize, panelSize, TooltipPlacement.Bottom);

5. Fluent extension WithTooltip

File: sus-kit/Runtime/Extensions/TooltipExtensions.cs

A convenient way to hang a tooltip on any VisualElement:

csharp
// Simple text
myButton.WithTooltip("Save game");

// Indicating placement
iconButton.WithTooltip("Settings", TooltipPlacement.Right);

// Rich card
var entry = new SusTooltipCardEntry
{
    Title = "Fireball",
    Description = "Damage: 50-70\nRange: 3 cells\nCooldown: 2 turns",
    IconAlias = "fire",
};
spellIcon.WithTooltip(entry);

// Custom VisualElement (lazy factory) — any card on OverlayHost Tooltip layer
slot.WithTooltip(() => BuildItemTooltip(item), TooltipPlacement.Right);

How it works:

  • PointerEnterEventservice.ShowAtElement / ShowContent (with delay ShowDelayMs).
  • PointerLeaveEventservice.HideAfterDelay() (through HideDelayMs).
  • When the element is detached (DetachFromPanelEvent) → service.Hide().

ShowContent(target, contentFactory, placement) builds the card after the show delay and positions it like text tooltips (auto-flip + clamp).


6. SusTooltipCardEntry

File: sus-kit/Runtime/Models/SusTooltipCardEntry.cs

csharp
public class SusTooltipCardEntry
{
    public string Title;        // Heading
    public string Description;  // Description (multiline)
    public string IconAlias;    // Alias of icons from SusIconRegistry, for example "info-circle"
    public string FooterText;   // Footer text
}

Used with ShowRich:

csharp
service.ShowRich(new SusTooltipCardEntry
{
    Title = "Sword of justice",
    Description = "Attack: 15-20\nType: slashing",
    IconAlias = "sword",
    FooterText = "One-handed"
});

7. Integration with router

Clearing the tooltip when changing the route - via beforeEach-guard:

csharp
// In SusRouter.Init()
ModalService.CloseAll();                     // modals
TooltipService.Instance?.Hide();             // active tooltip

When detaching the target element, the tooltip is automatically hidden (WithTooltip hangs DetachFromPanelEvent).

Theming (--sus-tooltip-*)

Defined in SusTooltip.sharq <​style> (override in project USS on .sus-tooltip__card):

TokenDefault role
--sus-tooltip-bgCard background
--sus-tooltip-radiusCorner radius
--sus-tooltip-border / --sus-tooltip-border-hoverOutline
--sus-tooltip-padCard padding
--sus-tooltip-header-bgHeader strip (also set by rarity)

Rarity modifiers on tip + card: sus-tooltip--rarity-common|uncommon|rare|epic|legendary|exotic (set by inventory / item cards for hover). Header strip color comes from --sk-rarity-* in suskit-tokens.uss:

ClassToken
sus-tooltip--rarity-common--sk-rarity-common
sus-tooltip--rarity-uncommon--sk-rarity-uncommon
sus-tooltip--rarity-rare--sk-rarity-rare
sus-tooltip--rarity-epic--sk-rarity-epic
sus-tooltip--rarity-legendary--sk-rarity-legendary
sus-tooltip--rarity-exotic--sk-rarity-exotic

Override --sk-rarity-* (or --thm-rarity-*) in the consumer theme USS.


8. Full connection example

csharp
// 1. Bootstrap
var overlay = SusBootstrap.GetOrCreateOverlay(uiDocument.rootVisualElement);

var tooltip = new TooltipService();
tooltip.Init(overlay);
tooltip.CreateTooltip = () => new SusTooltip();
tooltip.CreateCard = () => new SusTooltipCard();
TooltipService.Instance = tooltip;

// 2. The router picks up OverlayHost
router.Init(overlay);

// 3. Components use WithTooltip
settingsButton.WithTooltip("Game Settings", TooltipPlacement.Left);
attackIcon.WithTooltip(new SusTooltipCardEntry { Title = "Attack", Description = "Basic attack" });
slot.WithTooltip(() => BuildCustomCard(item), TooltipPlacement.Right);

9. Tests

FileTestsWhat covers
sus-kit/Editor/Tests/TooltipPlacementTests.cs9 editmode ResolvePlacement, ResolvePosition, auto-flip on edges
sus-kit/Runtime/Tests/TooltipServicePlaymodeTests.cs4 playmode ShowAtElement → visibility, Hide, one tooltip, StartsHidden
sus-kit/Runtime/Tests/WithTooltipTests.cs4 playmode WithTooltipstring/rich/null-service/null-element
sus-kit/Editor/Tests/SusTooltipCardEntryTests.cs~3 editmodeProperties SusTooltipCardEntry

10. OverlayHost layers + SusOverlayManager helpers

Documented above: OverlayHost categories (Modal = 20, Tooltip = 30, Dropdown = 40, Toast = 45, Console = 50). Tooltips and dropdowns stay above modals so popups from inside a dialog remain visible.

OverlayHost is already the portal host for kit tooltips (TooltipService.Init(overlayHost)OverlayCategory.Tooltip). In addition, SusOverlayManagerkeeps two convenience layers inside a panel for component-local dropdowns / tooltips:

panel.visualTree
├── ...app content...
├── sus-overlay-popup     ← Dropdown/Select (Show/Hide)
└── sus-overlay-tooltip   ← SusTooltip (ShowTooltip/HideTooltip)
                           ↑ always above the popup → the tooltip is visible on top of the dropdowns
  • Popup layer: one active, click-outside, scroll tracking - SusOverlayManager.Show/Hide
  • Tooltip layer: pickingMode = Ignore, several active, closing by hover - SusOverlayManager.ShowTooltip/HideTooltip
  • Cleaning: DetachFromPanelEvent on the activator, panelDestroyed on the panel

Mapping to OverlayHost categories (when the app uses the shared portal):

  • Popup / select menus → OverlayCategory.Dropdown = 40
  • Tooltips → OverlayCategory.Tooltip = 30
  • Toasts → OverlayCategory.Toast = 45
  • API ShowTooltip / HideTooltipstays the same; host is OverlayHost or the manager helper

Open-core: Core & Router MIT · Kit & Game commercial