f6ae86617c
Co-authored-by: Copilot <copilot@github.com>
180 lines
6.4 KiB
Markdown
180 lines
6.4 KiB
Markdown
# Tooltip
|
|
|
|
A small text hint that appears when the user hovers over an element. Use it to label icon buttons, clarify abbreviations, or explain options that don't have visible text.
|
|
|
|
Tooltips are implemented entirely in CSS — no JavaScript required.
|
|
|
|
---
|
|
|
|
## Quick example
|
|
|
|
```csharp
|
|
new Tooltip(
|
|
text: "Delete item",
|
|
trigger: new Button("🗑", size: "icon", variant: "ghost"))
|
|
```
|
|
|
|
Hover over the button and the label "Delete item" appears above it.
|
|
|
|
---
|
|
|
|
## All the options
|
|
|
|
```csharp
|
|
public Tooltip(
|
|
string text,
|
|
IHtmxComponent trigger,
|
|
string position = "top")
|
|
```
|
|
|
|
| Parameter | What it does |
|
|
|---|---|
|
|
| `text` | The tooltip text. Plain text only — no HTML. |
|
|
| `trigger` | Any `IHtmxComponent` — this is the element the user hovers over. |
|
|
| `position` | Where the tooltip appears: `"top"` (default), `"bottom"`, `"left"`, or `"right"`. |
|
|
|
|
---
|
|
|
|
## Real-world examples
|
|
|
|
### Icon buttons in a toolbar
|
|
|
|
```csharp
|
|
new Tooltip(text: "Bold", trigger: new Button("B", size: "icon", variant: "ghost"))
|
|
new Tooltip(text: "Italic", trigger: new Button("I", size: "icon", variant: "ghost"))
|
|
new Tooltip(text: "Save", trigger: new Button("💾", size: "icon", variant: "ghost"))
|
|
```
|
|
|
|
### Right-aligned tooltip (near the left edge of the UI)
|
|
|
|
```csharp
|
|
new Tooltip(
|
|
text: "View help documentation",
|
|
trigger: new Button("?", size: "icon", variant: "outline"),
|
|
position: "right")
|
|
```
|
|
|
|
### Below the element
|
|
|
|
```csharp
|
|
new Tooltip(
|
|
text: "This cannot be undone",
|
|
trigger: new Button("Delete", variant: "destructive"),
|
|
position: "bottom")
|
|
```
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
Tooltip wraps the trigger in a `<span class="group">`. The tooltip text is an absolutely positioned `<span>` inside that wrapper with `opacity-0` by default and `group-hover:opacity-100` to fade it in. Because this is pure Tailwind CSS, there is no JavaScript involved and no initialisation needed for HTMX-swapped content.
|
|
|
|
### 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");
|
|
}
|
|
```
|