Skip to content

Tooltips (SusKit)

Service: TooltipService (sus-kit/Runtime/Services/) Extension: TooltipExtensions.WithTooltip() (sus-kit/Runtime/Extensions/) Model: SusTooltipCardEntry (sus-kit/Runtime/Models/)


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 the target VisualElement.
  • Rich (ShowRich) — a 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 isn't 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 — calling 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 that copies style sheets (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
  • Style copying: OverlayHost.AddToOverlay() calls CopyAncestorStyleSheets(), which copies the .g.uss style sheets from the nearest SusComponent ancestor before the element is detached with RemoveFromHierarchy(). This ensures the tooltip card keeps its styles after being teleported into the overlay.
  • Important: ShowTooltip() does NOT call RemoveFromHierarchy() before AddToOverlay() — doing so would break the ancestor chain before the styles are copied.
  • design-tokens.uss and suskit-base.uss are loaded directly onto OverlayHost itself — CSS variables and base styles are visible to every child 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

MethodSignatureDescription
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 the OverlayHost Tooltip layer. Lazy factory after the show delay; same placement / auto-flip as text.
ShowRich(SusTooltipCardEntry entry, Vector2? screenPosition = null)Rich card via CreateCard + Apply(entry). If position is omitted — centered on screen.
Hide()Hide immediately.
HideAfterDelay()Hide via HideDelayMs (default 200ms). Used on PointerLeave.

Properties

PropertyTypeDefaultDescription
InstanceTooltipService (static)Singleton for WithTooltip
ShowDelayMsProp<float>500Delay before showing (ms)
HideDelayMsProp<float>200Delay before hiding (ms)
OverlayOverlayHostPortal container
CreateTooltipFunc<VisualElement>Factory for SusTooltip (Sharq)
CreateCardFunc<VisualElement>Factory for SusTooltipCard (Sharq)
IsVisiblebool (read-only)Whether a tooltip is currently 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 the position is calculated, the tooltip is clamped to the panel edges (4px margin):

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

Pure functions for tests

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

// Resolve 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 attach a tooltip to any VisualElement:

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

// With explicit 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 the OverlayHost Tooltip layer
slot.WithTooltip(() => BuildItemTooltip(item), TooltipPlacement.Right);

How it works:

  • PointerEnterEventservice.ShowAtElement / ShowContent (after ShowDelayMs).
  • PointerLeaveEventservice.HideAfterDelay() (after 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 an icon from SusIconRegistry, e.g. "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 the router

The tooltip is cleared on route change via a beforeEach guard:

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

When the target element is detached, the tooltip hides automatically (WithTooltip listens for 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 the tip + card: sus-tooltip--rarity-common|uncommon|rare|epic|legendary|exotic (set by inventory / item cards on 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 wiring 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 the same 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 it covers
sus-kit/Editor/Tests/TooltipPlacementTests.cs9 editmodeResolvePlacement, ResolvePosition, auto-flip on edges
sus-kit/Runtime/Tests/TooltipServicePlaymodeTests.cs4 playmodeShowAtElement → visibility, Hide, single-tooltip rule, StartsHidden
sus-kit/Runtime/Tests/WithTooltipTests.cs4 playmodeWithTooltip string/rich/null-service/null-element
sus-kit/Editor/Tests/SusTooltipCardEntryTests.cs~3 editmodeSusTooltipCardEntry properties

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, SusOverlayManager keeps 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 dropdowns
  • Popup layer: one active at a time, click-outside, scroll tracking - SusOverlayManager.Show/Hide
  • Tooltip layer: pickingMode = Ignore, several can be active, closes on hover out - SusOverlayManager.ShowTooltip/HideTooltip
  • Cleanup: 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
  • The ShowTooltip / HideTooltip API stays the same; the host is either OverlayHost or the manager helper.