Files
Htmx/docs/Components/Tooltip.md
T
2026-05-04 19:57:48 +05:00

189 lines
6.8 KiB
Markdown

# 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 `<span>` 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 `<span>` via a custom slot and pass that as the trigger.
---
## Complete page example
**`Templates/ActionToolbarPage.htmx`**
```html
<div class="max-w-3xl mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Document editor</h1>
<div class="flex items-center gap-2 border rounded-md p-2 mb-6">
$$BoldBtn$$
$$ItalicBtn$$
$$UnderlineBtn$$
$$SepToolbar$$
$$UndoBtn$$
$$RedoBtn$$
</div>
<div class="border rounded-md p-4 min-h-64 prose">
$$EditorContent$$
</div>
</div>
```
**`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 = "<p>Start typing...</p>".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<IResult> HandleAsync(Query _, HttpContext ctx, CancellationToken ct)
=> ctx.WriteHtmxPage(new ActionToolbarPage(), title: "Document editor");
}
```