Skip to content

feat(theme): allow replacing slot classes with a function#6562

Draft
benjamincanac wants to merge 1 commit into
v4from
feat/theme-replace-classes
Draft

feat(theme): allow replacing slot classes with a function#6562
benjamincanac wants to merge 1 commit into
v4from
feat/theme-replace-classes

Conversation

@benjamincanac
Copy link
Copy Markdown
Member

🔗 Linked issue

Resolves #4953
Related to #6551

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Adds a function form for slot classes so you can fully replace a slot's defaults instead of merging onto them, which is the recurring ask in #4953. Today app.config.ui, the :ui prop and the class prop only ever merge onto the component defaults (tailwind-variants extend concatenates and twMerge only drops conflicting utilities), so non conflicting defaults always survive and you end up fighting the cascade.

Now any slot value can be a (defaults) => classes function. It receives the slot's resolved default classes and returns what to use in their place, while plain strings keep merging exactly as before so nothing changes for existing code.

<UButton :ui="{ label: () => 'text-3xl font-bold' }" />
// app.config.ts, applies to every instance
export default defineAppConfig({
  ui: {
    pageHeader: { slots: { title: () => 'text-xl font-bold text-pretty' } }
  }
})

It works in the :ui prop, the class prop, app.config.ui and <UTheme :ui>. The whole thing lives in the shared tv wrapper (an apply-trap Proxy that preserves extend, plus slot wrapping and construction-time directive extraction), so there are zero component changes. The function is also the safest possible encoding here since it survives defu, is never bound to :class and is never serialized.

Performance is a non issue: the wrapper adds about 0.24µs per build plus four slot calls on the no replace path (a couple of typeof checks then a delegate to the original slot fn), and the build is already memoized inside each component's computed. The full suite passes with byte identical snapshots.

This pairs with the build-time theme.unstyled option from #6551 (global strip) to give a full three tier story: global, per instance and per slot. Note the docs cross link to theme.unstyled only resolves once #6551 lands.

📝 Checklist

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

Slot classes set through `:ui`, the `class` prop and `app.config.ui` can now
be a `(defaults) => classes` function that replaces the slot's default classes
instead of merging onto them. Strings keep their current merge behavior.

Implemented centrally in the `tv` wrapper (apply-trap Proxy + slot wrapping +
construction-time directive extraction), so no component changes are needed.

Resolves #4953
@github-actions github-actions Bot added the v4 #4488 label Jun 5, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 5, 2026

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

commit: b97baba

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🎨 Allow replacing all UI classes with new set of classes?

1 participant