Skip to content

fix(Link): ensure single-root rendering for v-show and $el resolution#6310

Merged
benjamincanac merged 1 commit intov4from
fix/link-vshow-template-ref
Apr 7, 2026
Merged

fix(Link): ensure single-root rendering for v-show and $el resolution#6310
benjamincanac merged 1 commit intov4from
fix/link-vshow-template-ref

Conversation

@benjamincanac
Copy link
Copy Markdown
Member

@benjamincanac benjamincanac commented Apr 7, 2026

🔗 Linked issue

Resolves #5987, resolves #5108, resolves #4154

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

When custom is true, <template> wrappers around <slot> and NuxtLink's direct slots.default() return produce Fragment root nodes. Vue cannot apply runtime directives (v-show) or resolve $el through Fragments, causing #5987 and #5108.

Fix:

  • Replace <template v-if="custom"> with <Slot v-if="custom"> (from Reka UI) in all 4 Link components to flatten slot Fragments into single elements.
  • In the Nuxt override, bypass NuxtLink for external links (isInternalLink computed) since NuxtLink's external+custom path always returns a Fragment. External link props (href, rel, target) are computed directly.
  • Non-link buttons (!to) now skip NuxtLink entirely, removing unnecessary route tracking that caused all UButton instances to re-render on route changes (Performance Issue Rendering Large List of UButtons – flushJobs Extremely Slow #4154).

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

The Link component logic was changed to explicitly determine internal vs external links via a new isInternalLink computed property and to set external rel via externalRel. Rendering was refactored into three branches: internal links (use Router/Nuxt link behavior), external links with a custom slot, and external links with default ULinkBase rendering. isLinkActive and resolveLinkClass accept empty-arg calls and early-return when to is missing. Framework overrides (inertia, none, vue-router) import and use a Slot wrapper from reka-ui for the custom slot. Tests were extended with external/internal link permutations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: replacing template fragments with Slot components to ensure single-root rendering for v-show and $el resolution.
Linked Issues check ✅ Passed The code changes directly address the linked issues: replacing with fixes v-show in #5987 and template ref resolution in #5108 by ensuring single-root elements. Out of Scope Changes check ✅ Passed All changes are scoped to the Link component and its overrides, plus test additions. No unrelated modifications outside the component's responsibility were introduced. Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Description check ✅ Passed The description clearly explains the root cause (Fragment rendering issues with custom slots), lists the specific fixes applied to multiple Link component variants, and references three linked issues being resolved.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/link-vshow-template-ref

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 7, 2026

npm i https://pkg.pr.new/@nuxt/ui@6310

commit: 67f31af

@benjamincanac
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/components/Link.spec.ts (1)

7-26: Consider adding tests for v-show and template ref behavior.

The PR's primary objective is fixing v-show directives and $el resolution. The current tests verify rendering but don't explicitly test these behaviors. Consider adding integration tests that:

  1. Apply v-show="false" and verify the element is hidden (display: none)
  2. Use a template ref and verify $el resolves to the DOM element
💡 Example test for v-show behavior
it('supports v-show directive', async () => {
  const wrapper = await mountSuspended({
    components: { Link },
    template: '<Link v-show="visible" to="/" custom v-slot="props"><a v-bind="props">Test</a></Link>',
    data: () => ({ visible: false })
  })
  
  expect(wrapper.find('a').isVisible()).toBe(false)
})

Would you like me to help generate more comprehensive tests for the v-show and template ref scenarios that this PR addresses?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/Link.spec.ts` around lines 7 - 26, Add integration tests to
cover v-show and template ref/$el resolution for the Link component: create one
test that mounts Link via mountSuspended (or the existing renderEach pattern)
with v-show="visible" (initially false) and a slot that renders an <a> element
bound to slot props, then assert the rendered anchor has display: none or
isVisible() === false; create another test that renders Link using a template
ref (e.g., ref="linkRef") and after mount assert that
wrapper.vm.$refs.linkRef.$el (or the ref value returned) resolves to the actual
DOM element for the underlying anchor or root element; locate tests near other
Link specs using renderEach and name them like "supports v-show directive" and
"resolves template ref to DOM element" to keep coverage for the v-show/$el
fixes.
src/runtime/components/Link.vue (1)

207-243: Consider adding data-slot attributes for slot identification.

Per the coding guidelines for src/runtime/components/**/*.vue, template elements should have data-slot="name" attributes for slot identification. The Slot and ULinkBase elements in this file don't have these attributes.

💡 Example addition
   <NuxtLink v-if="isInternalLink" v-slot="{ href, navigate, route: linkRoute, isActive, isExactActive, ...rest }" v-bind="nuxtLinkProps" :to="to" custom>
-    <Slot v-if="custom">
+    <Slot v-if="custom" data-slot="link">
       <slot
         ...
       />
     </Slot>
     <ULinkBase
       v-else
+      data-slot="link"
       ...
     >

As per coding guidelines: "Add data-slot="name" attributes on all template elements for slot identification"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Link.vue` around lines 207 - 243, The Slot and
ULinkBase template elements inside the NuxtLink custom rendering need data-slot
attributes for slot identification; add data-slot="custom" to the <Slot> element
used when custom is true and add data-slot="default" to the <ULinkBase> element
used for the non-custom rendering, keeping the rest of the v-bind and props
unchanged (locate the <Slot> and <ULinkBase> usages within the NuxtLink block to
apply).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/vue/overrides/vue-router/Link.vue`:
- Around line 222-236: The custom external Slot branch in Link.vue currently
doesn't provide a navigate handler; update the Slot v-else-if="custom" block to
include a navigate function (matching the behavior used in the "none" override)
in the v-bind object so custom slots receive navigate for external links;
implement navigate to call the same external navigation logic used elsewhere in
this component (respecting isExternal, href/to, rel, target and disabled) and
pass it as navigate alongside active, isExternal, href, etc., so custom-injected
handlers can trigger navigation consistently.

---

Nitpick comments:
In `@src/runtime/components/Link.vue`:
- Around line 207-243: The Slot and ULinkBase template elements inside the
NuxtLink custom rendering need data-slot attributes for slot identification; add
data-slot="custom" to the <Slot> element used when custom is true and add
data-slot="default" to the <ULinkBase> element used for the non-custom
rendering, keeping the rest of the v-bind and props unchanged (locate the <Slot>
and <ULinkBase> usages within the NuxtLink block to apply).

In `@test/components/Link.spec.ts`:
- Around line 7-26: Add integration tests to cover v-show and template ref/$el
resolution for the Link component: create one test that mounts Link via
mountSuspended (or the existing renderEach pattern) with v-show="visible"
(initially false) and a slot that renders an <a> element bound to slot props,
then assert the rendered anchor has display: none or isVisible() === false;
create another test that renders Link using a template ref (e.g., ref="linkRef")
and after mount assert that wrapper.vm.$refs.linkRef.$el (or the ref value
returned) resolves to the actual DOM element for the underlying anchor or root
element; locate tests near other Link specs using renderEach and name them like
"supports v-show directive" and "resolves template ref to DOM element" to keep
coverage for the v-show/$el fixes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aeaf6b35-1e1c-454e-921e-4583df1e0905

📥 Commits

Reviewing files that changed from the base of the PR and between 6f40f34 and 67f31af.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Link-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Link.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • src/runtime/components/Link.vue
  • src/runtime/vue/overrides/inertia/Link.vue
  • src/runtime/vue/overrides/none/Link.vue
  • src/runtime/vue/overrides/vue-router/Link.vue
  • test/components/Link.spec.ts

Comment thread src/runtime/vue/overrides/vue-router/Link.vue
@benjamincanac benjamincanac merged commit 2c4ff35 into v4 Apr 7, 2026
18 checks passed
@benjamincanac benjamincanac deleted the fix/link-vshow-template-ref branch April 7, 2026 14:06
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
…l` resolution (nuxt#6310)"

This reverts commit 20804d103c9d803137149ef1be312f9c3a29ecc9.
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
…l` resolution (nuxt#6310)"

This reverts commit 54af6ebdf61aa66ef3480ddb07d0c9fe1ad6935a.
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
…l` resolution (nuxt#6310)"

This reverts commit 20804d103c9d803137149ef1be312f9c3a29ecc9.
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
J-Michalek added a commit to J-Michalek/ui that referenced this pull request Apr 9, 2026
…l` resolution (nuxt#6310)"

This reverts commit 54af6ebdf61aa66ef3480ddb07d0c9fe1ad6935a.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

1 participant