Repository navigation
[Feature] Tooltip: match shadcn (Base UI + vega) - #654
Merged
Merged
Conversation
TooltipContent is now a role="tooltip" popover="manual" with vega's classes (bg-foreground text-background, text-xs, max-w-xs, inline-flex gap-1.5) and an arrow placed by Floating UI's arrow(). The controller places the content right after the trigger instead of in <body>, so it stays inside its container, and adds flip() and Base UI's --transform-origin. TooltipTrigger becomes an inline-flex w-fit wrapper, so the hit area and the anchor are the button, and drops the stray variant="outline" attribute. The tooltip now opens on keyboard focus (focusin), closes on Escape, and stays open while the pointer is over it (WCAG 1.4.13). The focusable trigger gets aria-describedby, as the APG tooltip pattern asks. New options: Tooltip(open:, disabled:) with a ruby-ui--tooltip:change event, TooltipTrigger(delay:, close_delay:, close_on_click:) and TooltipContent(side:, side_offset:, align:, align_offset:). placement: still works as a fallback for side:. data-slot on every part. SidebarMenuButton hides its tooltip from its own sidebar's state, which fixes tooltips staying hidden when another sidebar on the page is expanded (follow-up from #644). Adds the audit map (with the drift baseline) and fixtures, updates the docs and rebuilds the MCP registry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Escape on a tooltip hidden by CSS (expanded or mobile sidebar) closes it without taking the key, so the Sheet or dialog around it still closes - Reset hover and press state on disconnect, since Stimulus reuses the controller when the element comes back - A press during the open delay cancels the pending open - align_offset: also shifts a centered tooltip (Floating UI crossAxis) - The arrow takes the resolved physical side, so logical sides point the right way in RTL Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
- Coerce open:/disabled: to real booleans, so nil doesn't render an empty value that Stimulus reads as true - An open value set while disabled takes effect once re-enabled - No change events for the internal remount - placement: "inline-start"/"inline-end" stays a logical side - Docs: blur closes unless the pointer is still over the tooltip; the change event fires on open and close Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings Tooltip in line with shadcn (Base UI + vega), following the shadcn audit run on 2026-11-04. It also fixes the follow-up from #644: with two sidebars on one page, a collapsed sidebar's tooltips stayed hidden while the other sidebar was expanded.
What changes
role="tooltip"popover="manual"in the top layer, with vega's classes (bg-foreground text-background,text-xs,max-w-xs,inline-flex gap-1.5, kbd rules) and the arrow, placed by Floating UI'sarrow(). The controller now addsflip()and Base UI's--transform-origin.<body>, so it stays inside its container. SidebarMenuButton hides it from its own sidebar's state ([[data-slot=sidebar]:not([data-state=collapsed])_&]:hidden) and drops thebody:has(...)rule that any expanded sidebar matched.inline-flex w-fitwrapper, so the hit area and the anchor are the button (before, the whole row opened it and the tooltip was centered on the row). It no longer emits a strayvariant="outline"attribute.focusin, focus-visible only, as Base UI). Escape closes it, and the pointer can move onto it without closing it (WCAG 1.4.13). The focusable trigger getsaria-describedby, as the APG tooltip pattern asks; Base UI leaves that out. SidebarMenuButton skips it when the tooltip only repeats the label.Tooltip(open:, disabled:)plus aruby-ui--tooltip:changeevent,TooltipTrigger(delay: 0, close_delay: 0, close_on_click: true)andTooltipContent(side:, side_offset: 4, align:, align_offset: 0)with logical:inline_start/:inline_end.placement:keeps working as a fallback forside:. Every part has adata-slot.baseline tooltip 8a353667) and fixtures, updates the docs and rebuilds the MCP registry.A Codex review caught five issues, all fixed in the second commit: Escape on a tooltip hidden by CSS no longer swallows the key meant for the Sheet, hover state resets on reconnect, a press during the open delay cancels it,
align_offset:works withalign: :center, and the arrow points the right way in RTL.Screenshots
Measured in headed Chromium at 1280×800 with the same script on both fixtures, RubyUI and shadcn now give the same results: opens on Tab, closes on Escape, stays open with the pointer on it, popup centered on the button (x=254 on both), flips below at the top edge (y=42).
Follow-ups
align_offset:gap withalign: :center(it passes onlyalignmentAxis). Same one-line fix, left for a Popover PR.Textinside TooltipContent, andText's own size class overrides the tooltip'stext-xs. Their examples should use a plainpor string.delayto 600 ms; RubyUI defaults to 0 because shadcn's TooltipProvider sets 0. Revisit if RubyUI gets a provider.Test steps
cd gem && bundle exec rake,cd mcp && bundle exec rake test,cd audit && bundle exec rake,cd docs && bin/rails test./docs/tooltip: hover each example, Tab to a trigger (opens), press Escape (closes), move the pointer onto the tooltip (stays open), try the Sides row near the top of the window (flips).collapsible: :iconcollapsed and one expanded: hovering a collapsed button shows its tooltip; the expanded sidebar's buttons show none.Audit checklist, main
Audit checklist, this PR
🤖 Generated with Claude Code
Summary by cubic
Brings Tooltip in line with shadcn (Base UI + vega), adding keyboard focus, Escape dismissal, and hover support per WCAG 1.4.13, plus new placement and interaction options. Also fixes a follow-up from #644 where a collapsed sidebar's tooltips stayed hidden when another sidebar was expanded.
Key changes
role="tooltip"popover="manual"in the top layer with vega styling and an arrow; it sits right after the trigger, so it stays inside its container.open:,disabled:,delay:,close_delay:,close_on_click:,side:,side_offset:,align:, andalign_offset:options (placement:stays as a fallback).aria-describedby.open:anddisabled:are coerced to booleans sonildoesn't render an empty Stimulus value, and an open set while disabled takes effect once re-enabled.Written for commit 7000847. Summary will update on new commits.