Tooltip
A floating label describing an element on hover or focus.
Installation
Included in poetry-ui — available as
poetry_tooltip
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add tooltip
Default
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Hover" } %>
Add to library
<% end %>
Disabled trigger
<%# A tooltip explaining a disabled control. The trigger must be a WRAPPER
around the disabled button (compose seam): disabled buttons swallow
pointer events, so a tooltip on the button itself can never open. %>
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(compose: true) do |wiring| %>
<%= tag.span(class: "inline-block w-fit", **wiring) do %>
<%= poetry_button(variant: :outline, disabled: true) { "Publish" } %>
<% end %>
<% end %>
Fill in every required field before publishing
<% end %>
Keyboard
<%# Upstream's tooltip-keyboard: an icon trigger whose tooltip carries the
shortcut as a real poetry_kbd (the themed has-data-[slot=kbd] content
treatment picks it up). %>
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(variant: :outline, size: :"icon-sm", label: "Save changes") do %>
<%= poetry_icon(name: :save) %>
<% end %>
Save Changes <%= poetry_kbd { "S" } %>
<% end %>
Open
<%# Server-pinned open: the content renders visible with data-open and the
trigger carries aria-describedby from the first paint. %>
<%= poetry_tooltip(open: true) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Pinned" } %>
Server-rendered open
<% end %>
Provider row
<%# ONE provider scope wrapping a control row: the first tooltip opens
delayed, sweeping along the row opens instantly (the warm grace), and
the scope cools 300ms after leaving - the toolbar recipe. %>
<%= poetry_tooltip_provider(delay_duration: 700, class: "flex items-center gap-2") do %>
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(variant: :outline, size: :icon, label: "Add") do %>
<%= poetry_icon(name: :plus) %>
<% end %>
Add
<% end %>
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(variant: :outline, size: :icon, label: "Confirm") do %>
<%= poetry_icon(name: :check) %>
<% end %>
Confirm
<% end %>
<%= poetry_tooltip do |tooltip| %>
<% tooltip.with_trigger(variant: :outline, size: :icon, label: "Dismiss") do %>
<%= poetry_icon(name: :x) %>
<% end %>
Dismiss
<% end %>
<% end %>
Side bottom
<%# Radix Tooltip defaults side: :top; the arrow follows the resolved side. %>
<%= poetry_tooltip(side: :bottom, align: :start) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Below" } %>
side: :bottom, align: :start
<% end %>
Sides
<%# The side: option decides where the content and its arrow settle around
the trigger. One provider scope, all four placements in a row. %>
<%= poetry_tooltip_provider(class: "flex flex-wrap items-center gap-2") do %>
<%= poetry_tooltip(side: :top) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Top" } %>
Rests above the trigger
<% end %>
<%= poetry_tooltip(side: :right) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Right" } %>
Rests to the right
<% end %>
<%= poetry_tooltip(side: :bottom) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Bottom" } %>
Rests below the trigger
<% end %>
<%= poetry_tooltip(side: :left) do |tooltip| %>
<% tooltip.with_trigger(variant: :outline) { "Left" } %>
Rests to the left
<% end %>
<% end %>
With label
<%# Rich visual content with label: - the announced body is the
plain-text label; the visual children stay. %>
<%= poetry_tooltip(label: "Command S saves the document") do |tooltip| %>
<% tooltip.with_trigger(variant: :outline, label: "Save shortcut") { "Save" } %>
Saves the document <kbd>⌘S</kbd>
<% end %>
API
Poetry::Ui::Tooltip::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| content_class: | String | The bubble's class merge seam (caller classes win on conflicts). | |
| delay_duration: | Integer | The hover-open delay in ms; nil inherits the provider's (default 0). | |
| disable_hoverable_content: | Boolean | When true the bubble closes as the pointer leaves the trigger - it cannot be hovered into; nil inherits the provider. | |
| label: | String | Plain-text announcement override for rich content (the visual children stay; the announced body becomes this text). | |
| open: | Boolean | defaults to false |
Server-renders the tooltip open. |
Slots
| Writer | Description |
|---|---|
| with_trigger | The described control - commonly a poetry Button (with_trigger(variant: :outline) { \"Hover\" }). The slot owns the state + timing wiring regardless of the composed content. NO aria-haspopup/expanded/controls - the tooltip is invisible as a popup; aria-describedby is written by the controller on open (and server-rendered only when open: true). |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=tooltip] | Root wrapper around the trigger and the bubble |
| [data-slot=tooltip-content] | The role=tooltip bubble - positioning, animation, and the open state ride here |
| [data-slot=tooltip-arrow] | The arrow wrapper (aria-hidden) - popper pins it to the bubble's anchor-facing edge and rotates it toward the anchor |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| tooltip-content | data-open | bubble is open (the controller flips the pair at runtime) | — |
| tooltip-content | data-closed | bubble is closed (the server-rendered state; hidden rides along) | — |
| tooltip-content | data-instant | the open skipped the delay - warm-grace/programmatic or keyboard focus (runtime-only; absent on a delayed open) | delay · focus |
| tooltip-content | data-side | always - the side (initial placement, re-resolved live by popper after flip) | top · right · bottom · left |
| tooltip-content | data-align | always - the alignment (re-resolved live by popper) | start · center · end |
| tooltip-arrow | data-side | written by popper alongside the content's - the resolved side, for per-side restyling | top · right · bottom · left |
CSS variables
| Part | Variable | Description |
|---|---|---|
| tooltip-content | --transform-origin | the anchor-facing origin popper writes for scale-in animation |
| tooltip-content | --available-width | viewport space left for the bubble (popper, post-flip) |
| tooltip-content | --available-height | viewport space left for the bubble (popper, post-flip) |
| tooltip-content | --anchor-width | the anchor's measured width (popper) |
| tooltip-content | --anchor-height | the anchor's measured height (popper) |
Wiring
The Stimulus surface each element carries — declared in the component, verified against rendered DOM in CI. Bare actions fire on the element's default event.
| Element | Controller | Wiring |
|---|---|---|
| root | poetry--core--tooltip | registers · value open · value delay_duration (unless) · value disable_hoverable_content (unless) |
| root | poetry--core--popper | registers · value side · value align · value side_offset · value align_offset · value avoid_collisions |
| trigger | poetry--core--tooltip | pointerMove on pointermove · pointerLeave on pointerleave · pointerDown on pointerdown · clickClose on click · focusOpen on focus · blurClose on blur |
| trigger | poetry--core--popper | target anchor |
| content | poetry--core--popper | target content |
| arrow | poetry--core--popper | target arrow |