Tooltip
Thymeleaf source
<sl:tooltip> <sl:button variant="outline">Hover</sl:button> <sl:tooltip-content>Add to library</sl:tooltip-content></sl:tooltip>Rendered HTML
<span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline">Hover</button> <span class="tooltip-content" role="tooltip" popover="manual"> Add to library <span class="tooltip-arrow" aria-hidden="true"></span> </span></span><sl:tooltip> wraps the element it describes, the trigger, and a <sl:tooltip-content> after it. The trigger is
the first element inside: a <sl:button>, a link, anything that takes focus.
<sl:tooltip> <sl:button variant="outline">Hover</sl:button> <sl:tooltip-content>Add to library</sl:tooltip-content></sl:tooltip>The tooltip shows when the pointer rests on the trigger for a moment (delay, 300 ms by default), and at once when
the keyboard focuses it or when another tooltip hid a moment ago. It stays while the pointer moves onto it, so it can
be read when zoomed in, and hides when the pointer leaves both, when the trigger loses focus or is pressed, and on
Escape. Escape then does nothing else, so a dialog or menu around the trigger stays open. Showing a tooltip never
closes an open menu or popover either, so an item in a dropdown menu can have one. One tooltip shows at a time, and
touch screens never show one.
<sl:tooltip> itself has no box, so the trigger keeps its place in the layout, in a toolbar or in running text.
Describes, does not name
Section titled “Describes, does not name”The tooltip describes its trigger (aria-describedby, added to any description the trigger already has), so a screen
reader reads it after the trigger’s name. It does not replace the name: an icon-only button keeps its aria-label.
Thymeleaf source
<sl:tooltip> <sl:button variant="outline" size="icon" aria-label="Save"><sl:icon name="save"/></sl:button> <sl:tooltip-content> Save changes <sl:kbd-group><sl:kbd>Ctrl</sl:kbd><sl:kbd>S</sl:kbd></sl:kbd-group> </sl:tooltip-content></sl:tooltip>Rendered HTML
<span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline" data-size="icon" aria-label="Save"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"></path> <path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"></path> <path d="M7 3v4a1 1 0 0 0 1 1h7"></path> </svg> </button> <span class="tooltip-content" role="tooltip" popover="manual"> Save changes <kbd class="kbd-group"> <kbd class="kbd">Ctrl</kbd> <kbd class="kbd">S</kbd> </kbd> <span class="tooltip-arrow" aria-hidden="true"></span> </span></span>When it repeats the name
Section titled “When it repeats the name”A tooltip that only shows an icon button’s name (the same words as its aria-label) would make a screen reader say
them twice: “Edit, button, Edit”. mode="label" says the tooltip repeats the name: it is hidden from assistive
technology (aria-hidden on the content) and describes nothing. It still shows on hover and keyboard focus.
<sl:tooltip mode="label"> <sl:button variant="outline" size="icon" aria-label="Edit"><sl:icon name="pencil"/></sl:button> <sl:tooltip-content>Edit</sl:tooltip-content></sl:tooltip>Thymeleaf source
<sl:tooltip mode="label"> <sl:button variant="outline" size="icon" aria-label="Edit"><sl:icon name="pencil"/></sl:button> <sl:tooltip-content>Edit</sl:tooltip-content></sl:tooltip><sl:tooltip mode="label"> <sl:button variant="outline" size="icon" aria-label="Delete"><sl:icon name="trash"/></sl:button> <sl:tooltip-content>Delete</sl:tooltip-content></sl:tooltip>Rendered HTML
<span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline" data-size="icon" aria-label="Edit"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path> <path d="m15 5 4 4"></path> </svg> </button> <span class="tooltip-content" role="tooltip" popover="manual" aria-hidden="true"> Edit <span class="tooltip-arrow" aria-hidden="true"></span> </span></span><span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline" data-size="icon" aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> <path d="M10 11v6"></path> <path d="M14 11v6"></path> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path> <path d="M3 6h18"></path> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path> </svg> </button> <span class="tooltip-content" role="tooltip" popover="manual" aria-hidden="true"> Delete <span class="tooltip-arrow" aria-hidden="true"></span> </span></span>The sidebar’s menu buttons use this for their labels while it is collapsed to icons.
Keep the text short and plain. A tooltip cannot hold links or buttons (it hides when the pointer or focus leaves), and nobody on a touch screen sees it, so never put information in it that is not available another way. For richer content that the user can interact with, use a popover.
A disabled button fires no pointer events and cannot take focus, so its tooltip never shows. Wrap it in an element
that takes focus (<span tabindex="0">) when the tooltip explains why it is disabled, or better, say why next to it.
Position
Section titled “Position”The tooltip shows above its trigger, centred on it. side="top|right|bottom|left" and align="start|center|end" on
<sl:tooltip-content> change that, and it flips to the other side when there is no room, its arrow with it. right
and left are the end and the start of a line.
Thymeleaf source
<sl:tooltip> <sl:button variant="outline">Top</sl:button> <sl:tooltip-content>Shown above</sl:tooltip-content></sl:tooltip><sl:tooltip> <sl:button variant="outline">Right</sl:button> <sl:tooltip-content side="right">Shown after</sl:tooltip-content></sl:tooltip><sl:tooltip> <sl:button variant="outline">Bottom</sl:button> <sl:tooltip-content side="bottom">Shown below</sl:tooltip-content></sl:tooltip><sl:tooltip> <sl:button variant="outline">Left</sl:button> <sl:tooltip-content side="left">Shown before</sl:tooltip-content></sl:tooltip>Rendered HTML
<span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline">Top</button> <span class="tooltip-content" role="tooltip" popover="manual"> Shown above <span class="tooltip-arrow" aria-hidden="true"></span> </span></span><span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline">Right</button> <span class="tooltip-content" role="tooltip" popover="manual" data-side="right"> Shown after <span class="tooltip-arrow" aria-hidden="true"></span> </span></span><span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline">Bottom</button> <span class="tooltip-content" role="tooltip" popover="manual" data-side="bottom"> Shown below <span class="tooltip-arrow" aria-hidden="true"></span> </span></span><span class="tooltip" x-data="slTooltip"> <button class="button" type="button" data-variant="outline">Left</button> <span class="tooltip-content" role="tooltip" popover="manual" data-side="left"> Shown before <span class="tooltip-arrow" aria-hidden="true"></span> </span></span>It is placed with CSS anchor positioning, which every current browser supports (Chrome and Edge 133, Safari 26, Firefox 147). In older ones the tooltip’s small Alpine component places it from the trigger’s position instead.
Attributes
Section titled “Attributes”sl:tooltip
Section titled “sl:tooltip”| Attribute | Values | Default | Description |
|---|---|---|---|
mode | description label | description | What the text is to a screen reader: description adds it to the trigger as a description; label is for a text that repeats the trigger's name, such as an icon button's aria-label: it is hidden from assistive technology and describes nothing, so the name is not said twice. |
delay | number | 300 | How long the pointer rests on the trigger before the tooltip shows, in milliseconds. Keyboard focus shows it at once, and so does moving to another trigger right after a tooltip hid. |
Every prop can also be set from an expression: th:mode="${...}". Any other attribute passes through to the rendered element, and class is appended.
sl:tooltip-content
Section titled “sl:tooltip-content”| Attribute | Values | Default | Description |
|---|---|---|---|
side | top right bottom left | top | The side of the trigger it shows on. right and left are the end and the start of a line. |
align | start center end | center | How it lines up with the trigger along that side. |
Every prop can also be set from an expression: th:side="${...}". Any other attribute passes through to the rendered element, and class is appended.
JavaScript
Section titled “JavaScript”The tooltip is shown and hidden by a small Alpine component, slTooltip: <sl:tooltip> renders as
<span class="tooltip" x-data="slTooltip">.
- It needs Alpine. Browsers cannot show a popup on hover by themselves yet, so without Alpine the trigger still works but no tooltip shows. Alpine is included by default; see Alpine.
- It needs no Alpine plugin. If you load Alpine yourself (
shadleaf.assets.alpine=external), plain Alpine is enough. - Keep your own
x-dataoff<sl:tooltip>. An element has onex-data, and attributes on<sl:tooltip>end up on that span, so yours would replaceslTooltipand the tooltip would stop working. Put it on the trigger, or on an element around<sl:tooltip>.