# Tooltip A text hint that appears on hover. Implemented entirely in CSS using Tailwind's `group` and `group-hover` utilities — no JavaScript required. --- ## HTML structure ``` span.relative.inline-flex.items-center.group {trigger component rendered inline} span.tooltip-text.absolute.z-50.px-2.py-1.text-xs.rounded.bg-foreground.text-background .whitespace-nowrap.pointer-events-none .opacity-0.group-hover:opacity-100.transition-opacity.duration-150 .{position classes} {tooltip text} ``` --- ## CSS mechanics | Utility | Effect | |---|---| | `group` on wrapper | Enables `group-hover:*` utilities on descendants | | `opacity-0` | Tooltip invisible at rest | | `group-hover:opacity-100` | Tooltip fades in when the wrapper (group) is hovered | | `transition-opacity duration-150` | 150ms fade animation | | `pointer-events-none` | Tooltip itself doesn't interfere with hover detection | | `bg-foreground text-background` | Dark-on-light / light-on-dark automatically via CSS variables | | `whitespace-nowrap` | Prevents the tooltip from wrapping | | `z-50` | Floats above surrounding content | **Position classes by `position` parameter:** | Position | Classes | |---|---| | `top` (default) | `bottom-full mb-1.5 left-1/2 -translate-x-1/2` | | `bottom` | `top-full mt-1.5 left-1/2 -translate-x-1/2` | | `left` | `right-full mr-1.5 top-1/2 -translate-y-1/2` | | `right` | `left-full ml-1.5 top-1/2 -translate-y-1/2` | --- ## Constructor signature ```csharp public Tooltip( string text, IHtmxComponent trigger, string position = "top") ``` | Parameter | Description | |---|---| | `text` | Tooltip label (plain text; HTML not supported) | | `trigger` | Any `IHtmxComponent` that acts as the hover target | | `position` | `"top"` / `"bottom"` / `"left"` / `"right"` | --- ## Usage examples ### Icon button with tooltip ```csharp new Tooltip( text: "Delete item", trigger: new Button("🗑", size: "icon", variant: "ghost")) ``` ### Top/bottom/left/right positions ```csharp new Tooltip(text: "Above", trigger: new Button("Hover me"), position: "top") new Tooltip(text: "Below", trigger: new Button("Hover me"), position: "bottom") new Tooltip(text: "Left", trigger: new Button("Hover me"), position: "left") new Tooltip(text: "Right", trigger: new Button("Hover me"), position: "right") ``` ### Tooltip on an Avatar ```csharp new Tooltip( text: user.DisplayName ?? "Unknown user", trigger: new Avatar(fallback: user.Initials, src: user.AvatarUrl)) ``` ### Tooltip on a disabled-looking button ```csharp new Tooltip( text: "You need admin access", trigger: new Button( "Publish", variant: "default", hxAttrs: "disabled aria-disabled='true' tabindex='-1'")) ``` --- ## Tips and tricks - The tooltip text is plain text — HTML special characters in `text` will be HTML-encoded automatically. - Tooltip position may overflow the viewport if the trigger is near an edge — test all four positions and choose the one that fits. - Since there is no JS, the tooltip works even when JavaScript is disabled. - The trigger receives the `group` class implicitly — this means `group-hover:*` utilities on any child of the trigger will also activate on hover. Keep this in mind if the trigger component uses nested group utilities. - For touch devices the hover state is never triggered — consider providing the tooltip content elsewhere (e.g. as a `description` on a form field) if the information is essential. - To show a tooltip on a non-interactive element (e.g. a truncated table cell), wrap the element in a `` via a custom slot and pass that as the trigger. - To show a tooltip on a non-interactive element (e.g. a truncated table cell), wrap the element in a `` via a custom slot and pass that as the trigger. --- ## Complete page example **`Templates/ActionToolbarPage.htmx`** ```html

Document editor

$$BoldBtn$$ $$ItalicBtn$$ $$UnderlineBtn$$ $$SepToolbar$$ $$UndoBtn$$ $$RedoBtn$$
$$EditorContent$$
``` **`Templates/ActionToolbarPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ActionToolbarPage : ActionToolbarPageBase { private readonly IHtmxComponent _bold; private readonly IHtmxComponent _italic; private readonly IHtmxComponent _underline; private readonly IHtmxComponent _sep; private readonly IHtmxComponent _undo; private readonly IHtmxComponent _redo; private readonly byte[] _content; public ActionToolbarPage() { _bold = TooltipButton("Bold", "B", "font-bold"); _italic = TooltipButton("Italic", "I", "italic"); _underline = TooltipButton("Underline", "U", "underline"); _sep = new Components.Separator(orientation: "vertical", extraClasses: "mx-1 h-6"); _undo = TooltipButton("Undo (Ctrl+Z)", "↩", ""); _redo = TooltipButton("Redo (Ctrl+Y)", "↪", ""); _content = "

Start typing...

".ToUtf8Bytes(); } private static IHtmxComponent TooltipButton(string tip, string label, string textClass) => new Components.Tooltip( content: tip, trigger: new Components.Button( label, variant: "ghost", size: "icon", extraClasses: textClass)); protected override void RenderBoldBtn(HtmxRenderContext ctx) => _bold.Render(ctx.Next()); protected override void RenderItalicBtn(HtmxRenderContext ctx) => _italic.Render(ctx.Next()); protected override void RenderUnderlineBtn(HtmxRenderContext ctx) => _underline.Render(ctx.Next()); protected override void RenderSepToolbar(HtmxRenderContext ctx) => _sep.Render(ctx.Next()); protected override void RenderUndoBtn(HtmxRenderContext ctx) => _undo.Render(ctx.Next()); protected override void RenderRedoBtn(HtmxRenderContext ctx) => _redo.Render(ctx.Next()); protected override void RenderEditorContent(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_content); } ``` **GET handler** ```csharp [Handler] [MapGet("/editor")] public static partial class GetEditorHandler { public record Query(); private static Task HandleAsync(Query _, HttpContext ctx, CancellationToken ct) => ctx.WriteHtmxPage(new ActionToolbarPage(), title: "Document editor"); } ```