feat(Theme): add variants prop for component theming by Bobakanoosh · Pull Request #6031 · nuxt/ui
⚠️ Potential issue | 🔴 Critical
Bug: props.variants is always undefined — explicit component props are ignored.
ComponentVariantProps<C> maps variant keys (color, variant, block, square, …) as top-level properties. There is no .variants key on this type or on the actual ButtonProps object passed by the caller. As a result, props.variants ?? {} always evaluates to {}, and defu({}, themeOverrides) returns only the theme overrides — making it impossible for explicit component props (e.g., <UButton color="red">) to override theme-provided values.
Additionally, since no variant-related prop is actually read inside this computed, Vue won't track changes to props like color or variant, so the returned value won't reactively update when those props change.
Proposed fix — merge `props` directly with theme overrides
export function useComponentVariant<C extends keyof UIConfig>(name: C, props: ComponentVariantProps<C>): ComputedRef<ThemeVariantOverrides<ComponentVariants<C>>> {
const { variant } = injectVariantContext({ variant: computed(() => ({})) })
return computed(() => {
const themeOverrides = (get(variant.value, name as string) || {})
- return defu(props.variants ?? {}, themeOverrides)
+ return defu(props, themeOverrides)
})
}With defu(props, themeOverrides):
- Explicit prop values (e.g.,
color="red") win over theme overrides. undefinedprops (not passed by parent) fall through to theme overrides, thanks todefuskippingundefined.- Vue's reactivity tracking works because
defuenumerates the reactivepropsproxy.
If you want to limit over-tracking (any prop change triggers recompute), consider picking only variant-relevant keys before merging.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function useComponentVariant<C extends keyof UIConfig>(name: C, props: ComponentVariantProps<C>): ComputedRef<ThemeVariantOverrides<ComponentVariants<C>>> { | |
| const { variant } = injectVariantContext({ variant: computed(() => ({})) }) | |
| return computed(() => { | |
| const themeOverrides = (get(variant.value, name as string) || {}) | |
| return defu(props.variants ?? {}, themeOverrides) | |
| }) | |
| export function useComponentVariant<C extends keyof UIConfig>(name: C, props: ComponentVariantProps<C>): ComputedRef<ThemeVariantOverrides<ComponentVariants<C>>> { | |
| const { variant } = injectVariantContext({ variant: computed(() => ({})) }) | |
| return computed(() => { | |
| const themeOverrides = (get(variant.value, name as string) || {}) | |
| return defu(props, themeOverrides) | |
| }) | |
| } |
🤖 Prompt for AI Agents
In `@src/runtime/composables/useComponentVariant.ts` around lines 32 - 39, The
computed in useComponentVariant currently merges theme overrides with
props.variants which doesn't exist, so component props (e.g., color, variant)
are ignored and not tracked; update the merge to use the actual props object
(e.g., defu(props, themeOverrides)) so explicit props take precedence and Vue
reactivity tracks prop changes inside the computed returned by
useComponentVariant; optionally, to reduce over-tracking, pick only the
variant-related keys from props before calling defu.