Skip to content

Conversation

sandros94
Copy link
Member

πŸ”— Linked issue

Resolves #5232

❓ 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

Until the last update for the builder we weren't able to properly and strongly type various aspects about props, slots and emits. Now the issue has been fixed by slots were left unchanged (all being required and return was any). With this change normal use in the template won't change, but does greatly increase the typing experience when using render functions (required for the current UTable implementation).

πŸ“ Checklist

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

@sandros94 sandros94 self-assigned this Oct 15, 2025
@sandros94 sandros94 added bug Something isn't working typescript v4 #4488 labels Oct 15, 2025
@sandros94
Copy link
Member Author

@benjamincanac could I ask you to double check the various default slots and which could be optional and which must be required? For example most of Prose* components would require the default slot, as well the Form* components.

Copy link

pkg-pr-new bot commented Oct 16, 2025

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

commit: b9369eb

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

src/runtime/components/Avatar.vue (line 33):
The AvatarSlots interface was not updated to use the new typing pattern and still returns any.

View Details
πŸ“ Patch Details
diff --git a/src/runtime/components/Avatar.vue b/src/runtime/components/Avatar.vue
index 8d4b1211..be51d419 100644
--- a/src/runtime/components/Avatar.vue
+++ b/src/runtime/components/Avatar.vue
@@ -3,6 +3,7 @@ import type { AppConfig } from '@nuxt/schema'
 import theme from '#build/ui/avatar'
 import type { ChipProps, IconProps } from '../types'
 import type { ComponentConfig } from '../types/tv'
+import type { SlotsReturn } from '../types/utils'
 
 type Avatar = ComponentConfig<typeof theme, AppConfig, 'avatar'>
 
@@ -30,7 +31,7 @@ export interface AvatarProps {
 }
 
 export interface AvatarSlots {
-  default(props?: {}): any
+  default?(props?: {}): SlotsReturn
 }
 </script>
 

Analysis

Inconsistent typing pattern in Avatar.vue AvatarSlots interface

What fails: AvatarSlots interface in Avatar.vue uses outdated typing pattern default(props?: {}): any instead of the consistent default?(props?: {}): SlotsReturn pattern used across all other components

How to reproduce:

grep -r "default(props?: {}): any" src/runtime/components/  # Shows only Avatar.vue
grep -r "default?(props?: {}): SlotsReturn" src/runtime/components/  # Shows 31+ other components

Result: Avatar.vue is the only component not following the new typing standard, missing SlotsReturn import and optional slot syntax

Expected: Avatar.vue should follow the same consistent pattern as all other components: optional slots with ? and SlotsReturn return type, as defined in src/runtime/types/utils.ts

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

src/runtime/components/prose/CodeGroup.vue (lines 57-60):
The null/undefined handling for slot returns is incorrect and will cause runtime errors when no default slot is provided.

View Details
πŸ“ Patch Details
diff --git a/src/runtime/components/prose/CodeGroup.vue b/src/runtime/components/prose/CodeGroup.vue
index f891963e..9a8c203c 100644
--- a/src/runtime/components/prose/CodeGroup.vue
+++ b/src/runtime/components/prose/CodeGroup.vue
@@ -56,7 +56,7 @@ const items = computed<{
   rerenderCount.value
   let children = slots.default?.()
   if (!Array.isArray(children)) {
-    children = [children]
+    children = children ? [children] : []
   }
   return children?.flatMap(transformSlot).filter(Boolean) || []
 })

Analysis

Runtime error in CodeGroup.vue when slots.default() returns null/undefined

What fails: transformSlot() function in src/runtime/components/prose/CodeGroup.vue throws TypeError when accessing slot.type on null/undefined values from empty slots

How to reproduce:

// When slots.default?.() returns null/undefined, current code wraps as [null]/[undefined]
let children = null; // or undefined
if (!Array.isArray(children)) {
  children = [children]; // Creates [null] or [undefined]
}
children.flatMap(transformSlot); // Throws: "Cannot read properties of null (reading 'type')"

Result: TypeError: Cannot read properties of null/undefined (reading 'type') in transformSlot function

Expected: Should handle empty slots gracefully without runtime errors, returning empty array like AvatarGroup.vue pattern

Documentation: Vue 3 slots can return null/undefined in certain scenarios - components should handle these cases defensively per Vue 3 slot handling patterns


src/runtime/components/prose/CodeTree.vue (lines 68-71):
The null/undefined handling for slot returns is incorrect and will cause runtime errors when no default slot is provided.

View Details
πŸ“ Patch Details
diff --git a/src/runtime/components/prose/CodeTree.vue b/src/runtime/components/prose/CodeTree.vue
index fc4d3cee..20281270 100644
--- a/src/runtime/components/prose/CodeTree.vue
+++ b/src/runtime/components/prose/CodeTree.vue
@@ -67,7 +67,7 @@ const flatItems = computed<{
   rerenderCount.value
   let children = slots.default?.()
   if (!Array.isArray(children)) {
-    children = [children]
+    children = children ? [children] : []
   }
   return children?.flatMap(transformSlot).filter(Boolean) || []
 })

Analysis

Runtime error in CodeTree.vue when no default slot is provided

What fails: flatItems computed in CodeTree.vue throws "Cannot read properties of undefined (reading 'type')" when component has no default slot content

How to reproduce:

<CodeTree />  <!-- No slot content provided -->

Result: Runtime error occurs in transformSlot() function when it tries to access slot.type on undefined value

Expected: Component should render empty tree without errors, similar to AvatarGroup.vue behavior

The issue occurs because slots.default?.() returns undefined when no slot is provided, but the code wraps it as [undefined] instead of [], causing transformSlot to receive undefined values.

Fix: Updated the null handling to match the AvatarGroup.vue pattern that correctly handles this case.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

src/runtime/components/prose/Accordion.vue (lines 49-50):
Inconsistent handling of undefined slot returns compared to other prose components, causing unnecessary processing of undefined values.

View Details
πŸ“ Patch Details
diff --git a/src/runtime/components/prose/Accordion.vue b/src/runtime/components/prose/Accordion.vue
index fb9ac96e..22f5fa82 100644
--- a/src/runtime/components/prose/Accordion.vue
+++ b/src/runtime/components/prose/Accordion.vue
@@ -47,7 +47,7 @@ const items = computed<{
   rerenderCount.value
   let children = slots.default?.()
   if (!Array.isArray(children)) {
-    children = [children]
+    children = children ? [children] : []
   }
   return children?.flatMap(transformSlot).filter(Boolean) || []
 })
diff --git a/src/runtime/components/prose/Tabs.vue b/src/runtime/components/prose/Tabs.vue
index 1438d950..c85a4d2c 100644
--- a/src/runtime/components/prose/Tabs.vue
+++ b/src/runtime/components/prose/Tabs.vue
@@ -61,7 +61,7 @@ const items = computed<{
   rerenderCount.value
   let children = slots.default?.()
   if (!Array.isArray(children)) {
-    children = [children]
+    children = children ? [children] : []
   }
   return children?.flatMap(transformSlot).filter(Boolean) || []
 })

Analysis

Inconsistent undefined handling in slot processing causes unnecessary function calls

What fails: Accordion.vue and Tabs.vue use children = [children] when processing slot returns, creating [undefined] when slots.default?.() returns undefined, leading to unnecessary transformSlot(undefined) calls.

How to reproduce:

// When slots.default?.() returns undefined:
// Current approach calls transformSlot(undefined) 
// Optimized approach skips processing entirely

Result: Current approach: { result: [], transformCalls: 1 }, Optimized approach: { result: [], transformCalls: 0 }

Expected: Consistent with CodeGroup.vue and CodeTree.vue pattern: children = children ? [children] : [] to avoid processing undefined values and reduce unnecessary function calls.

Pattern confirmed in codebase: CodeGroup.vue and CodeTree.vue already implement this optimization correctly.

@benjamincanac benjamincanac changed the title type: fix slots typing fix(components): improve slots return types Oct 17, 2025
@benjamincanac
Copy link
Member

@sandros94 Should we also update all resolveComponent instances with #components import?

@sandros94
Copy link
Member Author

@sandros94 Should we also update all resolveComponent instances with #components import?

To provide typing? Shouldn't resolveComponent also resolve them?

Copy link
Member

It doesn't work on my end, but if you say resolveComponent works why bother rewriting all slots? πŸ€”

@sandros94
Copy link
Member Author

When writing in tsx with h it provides type error when passing invalid objects and classes as return, as they need proper destructuring/stringification (something we are used with via {{ ... }} in the template)

Copy link
Member

Yes but only when importing from #components not when using resolveComponent right?

@benjamincanac benjamincanac marked this pull request as draft October 17, 2025 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working typescript v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using render function on generic components makes slots required

2 participants