refactor: introduce AppTooltip, replace native title tooltips across docs + playground chrome — #495#621
Merged
Merged
Conversation
Introduce a docs-local AppTooltip built on the v0 Tooltip compound (Root/Activator/Content) and convert the Discord and GitHub app-bar links from native title attributes to it. The links keep their aria-label, so the accessible name is unchanged; the tooltip supplies the description via aria-describedby. Part of #495.
Add an app-local AppTooltip built on the v0 Tooltip compound and convert the share-link and settings buttons from native title attributes. These buttons previously relied on title for their accessible name, so an explicit aria-label is added on each; the tooltip text stays reactive so the share button still shows "Link copied!" feedback. The playground does not install createTooltipPlugin, so this exercises the useTooltip fallback context. Part of #495.
Declare a `disabled` prop on AppTooltip and forward it to Tooltip.Root. Tooltip.Activator merges its own attr bundle (which includes `disabled: undefined` when the root isn't disabled) over the caller's via mergeProps, so a caller's `:disabled` would otherwise be clobbered. Keeps the playground copy in sync with the docs one. Part of #495.
Replace native `title` browser tooltips on raw icon `<button>` elements across the docs app chrome with the AppTooltip wrapper (~37 sites over 20 files): releases/ask panels, example + mermaid toolbars, question/skill/ palette-preview controls, and app-shell buttons (search, settings, account, footer theme toggle, close button). Accessibility is preserved, not just moved: Tooltip wires aria-describedby (a description), not a name, so every icon-only button that relied on `title` for its accessible name gains an explicit aria-label. State- conveying toggles keep a reactive aria-label; copy/status buttons use a stable name with only the tooltip text flipping to "Copied!". AppTooltip also gains a `disabled` prop (forwarded to Tooltip.Root) so a caller's `:disabled` survives Tooltip.Activator's attr merge. AppCloseButton's style is un-scoped because AppTooltip renders a multi-root fragment and the parent scope-id is not forwarded to its button (see #359). Deliberately left native: submit buttons (Tooltip.Activator forces type=button, which would break form submit), buttons nested inside other activators (Popover/Discovery), non-button elements (span/div/a), and the high-cardinality palette-browse swatch grid (hundreds of Tooltip.Root instances). These are follow-up tranches. Part of #495.
Convert the wrapper components behind the page-features row to AppTooltip: - DocsMetaItem (phase, renderless, skill level, coverage, benchmark, last updated) — these titles add info beyond the visible label (coverage breakdown, commit hash), so they become real tooltips. The `·` separator moves from an adjacent-sibling (+) to a general-sibling (~) selector and is un-scoped, because AppTooltip renders a multi-root fragment: the tooltip content node sits between items and the parent scope-id is not forwarded (see #359). - DocsBadge (skill-level / mode badges) — as="span" tooltip. - DocsActionChip — the href chips (Edit page, Report a Bug, Open issues, View on GitHub) become as="a" tooltips; the Copy Markdown chip's title was identical to its visible label, so it is simply dropped rather than turned into a redundant tooltip. Part of #495.
Sweep the remaining native `title` tooltips on span/div/a elements: - DocsSearch — 9 result-row action controls (favorite / Ask AI / dismiss); they are role="button" spans that already carry aria-labels, so the name is preserved and only the tooltip description moves to AppTooltip. - DocsQuestion / DocsDiscoveryStep — the SKILLZ level badge span; the `.skillz-badge` style is un-scoped in both (AppTooltip renders a multi-root fragment, so the parent scope-id no longer reaches the activator — see #359). - DocsReleases — the two social <a> links. - DocsLastCommit — the commit <a> (tooltip shows the full sha; the visible text is the short slice, so it is genuine info, not a truncation mirror). - SkillMasteredBadge — the completed-skill <span>. - HomeDx — the theme color-swatch <div> (label on hover). Left native (correct): truncation-mirror titles on the Home copy/install command inputs (title echoes the truncated command), the DocsQuestion hint-reveal spoiler control, the Popover.Activator trigger, and the DocsReleases GnActionButton (genesis, separate tranche). Part of #495.
The Send button is a native type="submit", so the AppTooltip wrapper can't cover it — Tooltip.Activator forces type="button". Use Tooltip in renderless mode instead: bind attrs + the anchor styles onto the submit button so it keeps its type and native form submission while gaining the styled tooltip. This was the one docs-chrome site the icon-button sweep left on a native title. Relies on Tooltip.Activator exposing anchor styles on its renderless slot (#648). Part of #495.
The skill filter and theme selector are Popover.Activator triggers — a tooltip on the same element would collide with the popover's own CSS anchor-name. Wrap the activator in an AppTooltip as="span" instead: the tooltip anchors to the span, the popover to the inner button, and a click closes the tooltip while opening the popover. The account-settings avatar button (a plain button inside Discovery.Activator) converts directly. All three titles were redundant with the existing aria-label (or, for the theme selector, the current theme name), so accessible names are unchanged. Part of #495.
Same Popover.Activator pattern as the skill filter / theme selector: wrap the activator in AppTooltip as="span" so the tooltip and the popover anchor to separate elements. The wrapper div's :title="command" is left native — it is a truncation-mirror revealing the full command. Part of #495.
johnleider
marked this pull request as ready for review
July 19, 2026 19:08
…nav) A final onceover of native `title` in the docs app surfaced four sites the earlier sweeps missed: - AppFooter last-commit <a> (commit datetime) -> AppTooltip as="a". - AppBrowserIcon (:title -> AppIcon <i> fallthrough) -> AppTooltip as="span". - AppNavFooter freshness router-link -> AppTooltip as="span" wrap. - AppBar search button -- the title was fully redundant (aria-label + a visible "Search the docs..." label + a visible Ctrl+K kbd), so it is removed rather than turned into a redundant tooltip. Every other remaining `title=` in the docs app is a non-tooltip: heading text (API / settings sections), filename data props for code panes, or props feeding the already-converted meta/badge/chip wrappers -- plus the intentional exclusions (truncation-mirror inputs, the interactive hint spoiler, and the GnActionButton / Dialog.Close component tranches). Part of #495.
AppVersionChip's release link used Vue's same-name shorthand `:title` (the
"Last Released: <datetime>" hover), which the earlier sweep's `:title=` grep
missed. Wrap the AppLink in AppTooltip as="span" (AppLink is a routing
component, so it can't be the activator directly). The aria-label
("Latest release <tag>") is unchanged.
Part of #495.
Source openDelay/closeDelay/positionArea/disabled from Pick<TooltipRootProps, ...> and `as` from AtomProps['as'] instead of hand-retyping them, so the wrapper can't drift from the v0 Tooltip surface. Also widens `as` (was `string`) to accept a component, matching what Tooltip.Activator's `as` allows. Type-only; no runtime change. Deliberately NOT `extends TooltipRootProps`: that would surface props the wrapper doesn't forward (interactive, positionTry, namespace, open v-model, renderless) and inherit Root's `as` semantics (the Root element, not the activator). Part of #495.
Keep the playground AppTooltip copy in sync: forwarded prop types come from Pick<TooltipRootProps, ...> and `as` from AtomProps['as']. Type-only. Part of #495.
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.
Part of #495 — replacing native
titlebrowser tooltips with the v0Tooltipcompound across docs and playground.What this PR does
1.
AppTooltipwrapper (both apps)An app-local
AppTooltip(apps/docs/…/app/AppTooltip.vue,apps/playground/…/app/AppTooltip.vue), built directly on the v0Tooltip.Root/Activator/Contentcompound.textprop + default-slot trigger,as/positionArea/disabledprops, forwards$attrs(class/aria/listeners) toTooltip.Activator, surface-card styling matching theBenchmarkRow/DocsMaturityconvention.Why app-local, not
GnTooltipin genesis: docs depends on@paper/genesisbut playground depends only on@vuetify/v0, so one genesis wrapper can't serve both; and app chrome shouldn't couple to a released DS package. Matches the repo's existing per-app chrome duplication (AppCloseButtonexists in both apps).2. Conversions
AppBar(Discord/GitHub), playgroundPlaygroundAppBar(share-link + settings).DocsMetaItem(coverage breakdown, commit hash, …),DocsActionChip(page actions),DocsBadge(skill/mode badges).DocsMetaItem's·separator moved from an adjacent-sibling (+) to a general-sibling (~) selector and un-scoped, sinceAppTooltiprenders a multi-root fragment (see feat(genesis): add GnActionButton action-button primitive #359).DocsSearchresult actions (9), skillz badges, release/commit anchors, home color swatch.type="submit") — converted viaTooltiprenderless mode, bindingattrs+ the anchorstylesonto the submit button so it keeps itstypeand native form submission. This relies on theTooltip.Activatorrenderless anchor-styles fix (fix(Tooltip): expose anchor styles on Tooltip.Activator renderless slot #648), merged and included here.Accessibility (preserved, not just moved)
Tooltip.Activatorwiresaria-describedby— a description, not a name. So every icon-only trigger that relied ontitlefor its accessible name gained an explicitaria-label; state-conveying toggles keep a reactivearia-label; copy/status buttons use a stable name with only the tooltip text flipping to "Copied!".Tooltip.Contentrenders via the nativepopoverAPI (top layer), so tooltips stack above dialogs/overlays.Deliberately left native (correct, not tooltips / other tranches)
titles (DocsApi,DocsApiHover, api pages, settings-section headers) and filename data props (DocsCodeActions& friends).GnActionButton(versioned package + changeset),Popover.Activatortriggers (activator-on-activator), the palette-browse swatch grid (hundreds ofTooltip.Rootinstances), the playground editor chrome, and docs teaching examples.Verification
vue-tsc+ eslint clean on changed files.