Skip to content

feat(Theme): override component prop defaults#6031

Open
Bobakanoosh wants to merge 25 commits intonuxt:v4from
Bobakanoosh:feat/theme-variants
Open

feat(Theme): override component prop defaults#6031
Bobakanoosh wants to merge 25 commits intonuxt:v4from
Bobakanoosh:feat/theme-variants

Conversation

@Bobakanoosh
Copy link
Copy Markdown
Contributor

@Bobakanoosh Bobakanoosh commented Feb 13, 2026

Resolves #6359, resolves #2662, resolves #6335

❓ 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

Adds a :props prop to <UTheme> to override the default value of any prop on descendant components. Each key is typed against that component's own Props interface.

<UTheme
  :props="{
    button: { color: 'warning', loading: true },
    tooltip: { delayDuration: 0, arrow: true }
  }"
/>

📝 Checklist

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

@github-actions github-actions Bot added the v4 #4488 label Feb 13, 2026
@Bobakanoosh
Copy link
Copy Markdown
Contributor Author

@benjamincanac Here is a POC of the variants prop on UTheme, I pushed an example in the button playground.

Seeking guidance on the following, the types are a bit tricky:

  1. Passing this variant configuration to useFieldGroup
  2. Passing this variant configuration to useComponentIcons

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the per-component useComponentUI/uiProp pattern with a new useComponentProps(name, _props) across many runtime components so templates and class/slot overrides read from props.ui and component props from props.*. Adds a new useComponentProps composable with theme/context types and provisioning, expands Theme to accept optional ui and new variants and provide both contexts, renames the UI context type/identifier, updates CLI templates/commands to emit the new pattern and append entries to ThemeDefaults, adds an ESLint rule to rewrite bare template prop refs, and updates docs and examples for the new prop-driven theme workflow.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(Theme): override component prop defaults' clearly and concisely summarizes the main feature: enabling theme-level override of component prop defaults.
Description check ✅ Passed The PR description is directly related to the changeset, explaining the new :props feature on with a concrete usage example and linking to resolved issues.
Linked Issues check ✅ Passed The implementation addresses all three linked issues: #6359 (UTheme support for props), #2662 (override default props from AppConfig), and #6335 (Tooltip props configuration at app level).
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the :props feature on UTheme and integrating it across components via useComponentProps, with supporting documentation and CLI updates.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

🤖 Fix all issues with AI agents
In `@src/runtime/composables/useComponentVariant.ts`:
- Around line 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.
🧹 Nitpick comments (1)
src/runtime/components/Theme.vue (1)

20-26: Consider whether nested <UTheme> should merge contexts.

Currently, each <UTheme> fully replaces both the UI and variant contexts for its descendants. If a user nests <UTheme :variants="{ button: { color: 'red' } }"> inside <UTheme :variants="{ button: { variant: 'soft' } }">, the inner theme will lose the outer's variant: 'soft'.

This may be intentional for a POC, but worth considering whether defu-merging with the parent context (similar to how useComponentVariant merges props with theme overrides) would provide a more intuitive cascading behavior.

Comment thread src/runtime/composables/useComponentVariant.ts Outdated
@benjamincanac benjamincanac changed the title feat(UTheme): variants poc feat(Theme): add variants prop for component theming Feb 15, 2026
@benjamincanac benjamincanac marked this pull request as draft February 16, 2026 11:59
@Bobakanoosh
Copy link
Copy Markdown
Contributor Author

@benjamincanac Any feedback on this POC? Would love to proceed with implementing on all components.

Still Seeking guidance on the following, the types are a bit tricky:

  • Passing this variant configuration to useFieldGroup
  • Passing this variant configuration to useComponentIcons

@caiotarifa
Copy link
Copy Markdown
Contributor

We really need this.
Thank you for your work, @Bobakanoosh.

@benjamincanac
Copy link
Copy Markdown
Member

@Bobakanoosh Will look into it asap!

@benjamincanac benjamincanac changed the title feat(Theme): add variants prop for component theming feat(Theme): add variants prop Apr 21, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

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

commit: a98ea77

@benjamincanac
Copy link
Copy Markdown
Member

@Bobakanoosh I pushed some improvements on top of your work to unify theme context.

However, before rolling out to all components, I want to think about the broader direction. Right now variants only overrides TV styling variants (color, variant, size). But looking at #2662 and how Vuetify does it with their defaults provider, we could go further and let UTheme override any prop on any component, like <UTheme :tooltip="{ arrow: true }"> or <UTheme :accordion="{ collapsible: false }">.

A few open questions:

  1. Should UTheme have per-component props (:button, :tooltip) instead of a single nested variants object?
  2. Should UTheme only handle contextual/scoped overrides, and global app-wide defaults (Override default props from AppConfig #2662) live on UApp instead?

Thoughts? cc @sandros94 @jd-solanki

@sandros94
Copy link
Copy Markdown
Member

@Bobakanoosh I pushed some improvements on top of your work to unify theme context.

However, before rolling out to all components, I want to think about the broader direction. Right now variants only overrides TV styling variants (color, variant, size). But looking at #2662 and how Vuetify does it with their defaults provider, we could go further and let UTheme override any prop on any component, like <UTheme :tooltip="{ arrow: true }"> or <UTheme :accordion="{ collapsible: false }">.

A few open questions:

1. Should UTheme have per-component props (`:button`, `:tooltip`) instead of a single nested `variants` object?

2. Should UTheme only handle **contextual/scoped** overrides, and global app-wide defaults ([Override default props from `AppConfig` #2662](https://github.com/nuxt/ui/issues/2662)) live on UApp instead?

Thoughts? cc @sandros94 @jd-solanki

My personal gut feeling is your second option: a clear separation between the overrides from the global configs (UTheme and UApp respectively)

@benjamincanac
Copy link
Copy Markdown
Member

@sandros94 So you think we should keep UTheme only to override ui props and default variants props like color, variant and size?

@sandros94
Copy link
Copy Markdown
Member

@sandros94 So you think we should keep UTheme only to override ui props and default variants props like color, variant and size?

@benjamincanac yes indeed

@Bobakanoosh
Copy link
Copy Markdown
Contributor Author

@benjamincanac @sandros94

I don't see why not both - the problem UTheme solved was that the global theme configuration was not enough - we needed theming scoped to subset of the component tree.

The same could potentially be said for component prop defaults.

I'd propose:

<UTheme
    :ui="..."
    :variants="..."
    :components="{
         tooltip: {
              delayDuration: 150
         }
    }"
>
...
</UTheme

@benjamincanac in reference to your 1 option:

Should UTheme have per-component props (:button, :tooltip) instead of a single nested variants object?
variants does not serve the same purpose as prop defaults in my opinion. In UApp, we're not allowed to pass variant for a component type.

variants is a shared prop that practically every component has, just like ui, while other props are not.

Following this argument, one could say that we should even have a size prop that behaves the same as ui and variants, since it's shared amongst many components, but 🤷

@jd-solanki
Copy link
Copy Markdown
Contributor

jd-solanki commented Apr 23, 2026

Here are my thoughts:

  1. Adding variants as prop to UTheme is not appropriate. As we have ui prop, I suggest we introduce props prop:
<UTheme
  :ui="{}"
  :props="{
	tooltip: {
		delayDuration: 150
	}
  }"
/>

Why?

props makes it clear that it accept props for components similar to ui prop


  1. Should UTheme have per-component props (:button, :tooltip) instead of a single nested variants object?

Instead of per component prop, having single props prop allows you easily manage reactivity like watching for a single prop is easier than 50+ values we accept as prop.

  1. Should UTheme only handle contextual/scoped overrides, and global app-wide defaults (Override default props from AppConfig Override default props from AppConfig #2662) live on UApp instead?

Supporting global app-wide props is better idea and less confusing than selecting props like variant only. It also becomes AI to understand that props component will accept any prop and not just few selected which it has to go through docs.


  • I also have thought that in future v5, should we rename this to UTheme -> UConfig? This is because it configures the UI library with theming and component props.

Sweeps every component to use `useComponentProps` and the local proxy-aware
`useForwardProps`, finalizing the `<UTheme :props>` API introduced in
d12961b.

- Rename `useComponentUI` to `useComponentProps`. The proxy now drops the
  unused `_theme` argument and advertises `__v_isReactive` / `__v_raw` so
  `reactiveOmit`, `reactivePick`, and `toRefs` don't warn when given the
  proxy.
- Move `FormField.orientation`, `RadioGroup.orientation`, and
  `ChatTool.variant` from `theme.defaultVariants` into `withDefaults` —
  `theme.defaultVariants` is no longer in the proxy chain.
- Loosen Checkbox / RadioGroup template conditions back to
  `(!props.variant || props.variant === 'list')` so they tolerate
  `undefined` again.
- Pass raw `_props` (not the proxy) to `useFormField`, `useFieldGroup`, and
  `useAvatarGroup` in 9 components so the wrapping `<UFormField>` /
  `<UFieldGroup>` / `<UAvatarGroup>` keeps precedence over `<UTheme :props>`.
- Add an inline `nuxt-ui/no-bare-prop-refs` ESLint rule with autofix to
  enforce `props.x` access in templates of components using
  `useComponentProps`.
- Update CLI scaffolds, AGENTS.md, and the contributing guide to match the
  new pattern.
…rGroup

`useFormField`, `useFieldGroup`, and `useAvatarGroup` rely on the wrapping
component being the closer context (`_props.x ?? injected.x`). These two
tests pin down that wiring so any future regression to passing the proxy
`props` instead of raw `_props` fails loudly:

- `<UFieldGroup size="xl">` beats `<UTheme :props="{ button: { size: 'xs' }}}">`
- `<UAvatarGroup size="xl">` beats `<UTheme :props="{ avatar: { size: 'xs' }}}">`
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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/contributing/component-structure.md:
- Around line 60-63: The docs incorrectly imply useComponentProps resolves
theme.file defaultVariants into runtime props; update the guidance to call
useResolvedVariants(name, props, theme, ['variant']) wherever variant values
drive template logic (e.g., <component :is>, v-if, computed) so props.variant
reflects theme-file defaults; mention that tv()/defaultVariants only affect
classes and that app.config.ui.<name>.defaultVariants alone does not populate
props.variant, and show using useResolvedVariants after useComponentProps to
merge runtime-resolved variants.

In `@AGENTS.md`:
- Line 93: Update the documentation to remove "theme.defaultVariants" from the
runtime prop precedence described for useComponentProps(name, _props) — clarify
that theme.defaultVariants only affect tv() class resolution and are not read by
useComponentProps at runtime, and instruct authors to pass raw _props (not the
proxy) to useFormField / useFieldGroup to preserve injection precedence;
additionally add guidance to use useResolvedVariants(name, props, theme,
['variant']) when variant values are needed for template logic (v-if, <component
:is>, computed) and note that tv() defaultVariants do not supply runtime props
such as props.variant.

In `@cli/templates.mjs`:
- Around line 85-87: ${upperName}Props currently extends ${upperName}RootProps
exposing props the scaffold never forwards; change the public type to only
include forwarded keys (e.g. use Pick<${upperName}RootProps, 'as'> or the exact
forwarded keys) so ${upperName}Props matches what rootProps actually forwards,
and update the reactivePick(...) key list to derive from the same forwarded-key
set (ensure both the type alias and the reactivePick invocation reference the
same single source of truth for forwarded prop names).
- Around line 61-66: Detect when the generated component is a prose variant and
prefix lookups with "prose.": change the use of
useComponentProps('${camelName}', _props) and appConfig.ui?.${camelName} in the
ui computed to use the dotted key when prose is set (e.g.,
useComponentProps('prose.${camelName}', _props) and
appConfig.ui?.prose?.${camelName} fallbacking to appConfig.ui?.${camelName}), so
the proxy resolves nested theme/app-config entries; apply the same adjustment to
the other occurrence block (lines 111-119) that builds ui/theme lookups.
🪄 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: ae2d9214-b2e3-44ec-8cd3-70631485c94e

📥 Commits

Reviewing files that changed from the base of the PR and between e8f6242 and 09b8dfe.

📒 Files selected for processing (7)
  • .github/contributing/component-structure.md
  • AGENTS.md
  • cli/commands/make/component.mjs
  • cli/templates.mjs
  • cli/utils.mjs
  • docs/app/components/content/examples/theme/ThemePropsExample.vue
  • docs/content/docs/2.components/theme.md

Comment thread .github/contributing/component-structure.md
Comment thread AGENTS.md Outdated
Comment thread cli/templates.mjs Outdated
Comment thread cli/templates.mjs
… scaffold

Remove `theme.defaultVariants` from the documented `useComponentProps`
priority chain — it only feeds `tv()` class resolution, not runtime props.

Fix CLI scaffold for `--prose` (and `--content`): use dotted lookup
`useComponentProps('prose.<name>', _props)`, nested `appConfig.ui?.prose?.<name>`
fallback, the `'ui.prose'` `ComponentConfig` arg, and `../../` import paths
for the deeper directory.
Switch the `nuxt-ui/no-bare-prop-refs` ESLint rule from a static
interface walker (which couldn't follow `extends`/`Pick`/`Omit` past
imported types) to a binding-based check: in components using
`useComponentProps`, any free identifier in a template that isn't a
setup binding, slot-scoped variable, JS global, or PascalCase type
reference must be a prop and gets flagged.

The rule now catches inherited props from `Omit<LinkProps, …>`,
`extends UseComponentIconsProps`, reka-ui `*RootProps`, etc. — exactly
the gap that let these bugs slip through `<UTheme :props>`:

- Button: `disabled`, `avatar`
- Badge: `avatar`
- Tabs: `modelValue`, `defaultValue`, `activationMode`
- PinInput: `placeholder`, `modelValue`, `defaultValue`
- InputNumber: `min`, `max`, `step`, `required`
- Tree: `disabled` (×2), `selectionBehavior`
- CommandPalette: `loading`, `loadingIcon` (×2), `selectionBehavior`
- + 11 more components with assorted single bare refs

Each of these silently bypassed the proxy, so `<UTheme :props>`
overrides for those props were no-ops. Auto-fixed via `eslint --fix`.
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/runtime/components/EditorToolbar.vue (1)

132-138: ⚠️ Potential issue | 🟠 Major

Use resolved props.layout for runtime component selection.

Line 137 uses _props.layout, which bypasses the useComponentProps chain. That makes themed defaults for layout ineffective at runtime while ui still resolves from props.layout, causing behavior/style mismatch.

Suggested fix
 const Component = computed(() => {
   return ({
     bubble: BubbleMenu,
     floating: FloatingMenu,
     fixed: 'template'
-  }[_props.layout])
+  }[props.layout])
 })
As per coding guidelines: `src/runtime/components/*.vue`: Wrap raw props with `useComponentProps(name, _props)` to resolve the priority chain (explicit prop > `` > `withDefaults` > `app.config.ui..defaultVariants`).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/EditorToolbar.vue` around lines 132 - 138, The
computed Component currently reads layout directly from _props (bypassing the
props resolution chain), so change it to first wrap raw props using
useComponentProps with this component's name (e.g.,
useComponentProps('EditorToolbar', _props)) and then read layout from the
resolved props; update the Component computed to use resolvedProps.layout
instead of _props.layout so themed/defaulted values from useComponentProps are
honored (refer to the computed named Component, the raw _props, and the
useComponentProps helper).
src/runtime/components/content/ContentSearch.vue (2)

248-253: ⚠️ Potential issue | 🟠 Major

Register the shortcut from props, not _props.

Using _props.shortcut here bypasses the resolved priority chain, so UTheme :props="{ contentSearch: { shortcut: 'meta_j' } }" becomes a no-op.

Suggested fix
 defineShortcuts({
-  [_props.shortcut]: {
+  [props.shortcut]: {
     usingInput: true,
     handler: () => open.value = !open.value
   }
 })

As per coding guidelines, src/runtime/components/*.vue: Wrap raw props with useComponentProps(name, _props) to resolve the priority chain (explicit prop > <UTheme :props> > withDefaults > app.config.ui.<name>.defaultVariants).

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

In `@src/runtime/components/content/ContentSearch.vue` around lines 248 - 253, The
shortcut registration uses raw _props instead of resolved props, so change the
code to wrap incoming _props with useComponentProps('ContentSearch', _props) and
use that resolvedProps.shortcut when calling defineShortcuts; specifically,
create const resolvedProps = useComponentProps('ContentSearch', _props) near the
top of the setup and replace _props.shortcut with resolvedProps.shortcut in the
defineShortcuts handler (keeping the existing handler logic that toggles
open.value).

102-130: ⚠️ Potential issue | 🟠 Major

Import the proxy-aware useForwardProps from composables instead of reka-ui.

Line 98 imports useForwardProps from 'reka-ui', but commandPaletteProps and modalProps (lines 129–130) need the repo-local version from '../composables/useForwardProps'. The reka-ui helper filters out values not explicitly passed on the component's vnode, so theme defaults like fullscreen, close, and searchDelay supplied through useComponentProps disappear before reaching UModal and UCommandPalette. The repo-local version forwards all enumerable keys transparently, preserving proxy-injected values. Other similar components (Modal, Tooltip, Toast, Tree) correctly use the composables version.

Also, defineShortcuts reads _props.shortcut instead of props.shortcut, bypassing the theme/app/default priority chain.

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

In `@src/runtime/components/content/ContentSearch.vue` around lines 102 - 130,
Replace the incorrect import of useForwardProps from 'reka-ui' with the
repo-local composable and use it to build commandPaletteProps and modalProps so
proxy-injected/theme defaults (e.g., fullscreen, close, searchDelay) are
preserved; locate where commandPaletteProps and modalProps are created and
switch the imported symbol to ../composables/useForwardProps. Also update
defineShortcuts (or the place where shortcuts are derived) to read
props.shortcut instead of _props.shortcut so the theme/app/default resolution
via useComponentProps is respected.
src/runtime/components/DashboardSidebar.vue (1)

72-84: ⚠️ Potential issue | 🟡 Minor

Align the mode default with the documented API.

The prop docs say mode defaults to 'modal', but the runtime default here is 'slideover'. That mismatch will surface in generated docs and makes theme-level defaults harder to reason about.

As per coding guidelines, src/runtime/components/*.vue: Use withDefaults() for runtime prop defaults and JSDoc @defaultValue annotations for documentation.

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

In `@src/runtime/components/DashboardSidebar.vue` around lines 72 - 84, The
runtime default for the mode prop in the
withDefaults(defineProps<DashboardSidebarProps<T>>()) block is incorrect—change
the value from 'slideover' to 'modal' to match the documented API, and
add/update the JSDoc `@defaultValue` 'modal' on the DashboardSidebarProps<T> mode
prop declaration so docs and runtime defaults stay in sync.
src/runtime/components/InputTags.vue (1)

93-116: ⚠️ Potential issue | 🟠 Major

Theme defaults for form-state props are still bypassed here.

useFormField(_props) / useFieldGroup(_props) are fine for preserving raw explicit props, but the final size, color, highlight, and disabled state never falls back to props.*. That makes <UTheme :props="{ inputTags: { size: 'lg', color: 'warning', disabled: true } }"> a no-op for this component.

Suggested fix
-const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputTagsProps>(_props)
+const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color: formColor, id, name, highlight: formHighlight, disabled: formDisabled, ariaAttrs } = useFormField<InputTagsProps>(_props)
 const { orientation, size: fieldGroupSize } = useFieldGroup<InputTagsProps>(_props)
 const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
 
-const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)
+const inputSize = computed(() => fieldGroupSize.value ?? formFieldSize.value ?? props.size)
+const color = computed(() => formColor.value ?? props.color)
+const highlight = computed(() => formHighlight.value ?? props.highlight)
+const disabled = computed(() => formDisabled.value || props.disabled)

As per coding guidelines, src/runtime/components/*.vue: Wrap raw props with useComponentProps(name, _props) to resolve the priority chain (explicit prop > <UTheme :props> > withDefaults > app.config.ui.<name>.defaultVariants).

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

In `@src/runtime/components/InputTags.vue` around lines 93 - 116, The component is
calling useFormField and useFieldGroup with raw _props so theme/default
fallbacks from useComponentProps('inputTags', _props) are bypassed; replace the
_props arguments with the resolved props variable and ensure the ui and
inputSize computed use the resolved values (e.g., use inputSize.value ||
props.size, color.value || props.color, highlight.value ?? props.highlight,
disabled.value ?? props.disabled) so theme defaults from useComponentProps take
effect; update calls to useFormField(_props) -> useFormField(props) and
useFieldGroup(_props) -> useFieldGroup(props) and adjust the ui computed to fall
back to props.* where the form-field/group composables return undefined.
🤖 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/components/Button.vue`:
- Around line 62-70: The code currently passes pickLinkProps(props) through the
external useForwardProps which strips theme-injected values; update the import
to use the local helper from ../composables/useForwardProps so link props
injected via useComponentProps(...) are preserved. Specifically, where
useForwardProps is referenced for linkProps (used together with pickLinkProps
and props resolved by useComponentProps), replace the reka-ui import with the
local composable implementation to ensure to/href/target from UTheme :props are
not filtered out.

In `@src/runtime/components/ChatPrompt.vue`:
- Line 114: The template uses a logical OR fallback for the placeholder binding
in ChatPrompt.vue (the :placeholder expression using props.placeholder ||
t('chatPrompt.placeholder')), which treats an explicit empty string as missing;
change it to a nullish coalescing fallback (use ??) so an intentional '' is
preserved while still falling back when props.placeholder is null or undefined.
Ensure the updated expression references props.placeholder ??
t('chatPrompt.placeholder') in the template.

In `@src/runtime/components/ColorPicker.vue`:
- Around line 81-85: The runtime defaults for ColorPicker props were added via
withDefaults(defineProps<ColorPickerProps>()) — specifically throttle: 50 and
defaultValue: '#FFFFFF' — but the JSDoc for those props is missing `@defaultValue`
annotations; update the JSDoc on the ColorPickerProps prop declarations (or the
prop comments above the component) to add `@defaultValue` 50 for throttle and
`@defaultValue` '#FFFFFF' for defaultValue so docs match the runtime defaults.

In `@src/runtime/components/CommandPalette.vue`:
- Line 337: The code reads raw _props (e.g. in const fuseSearchTerm =
refDebounced(searchTerm, () => _props.searchDelay)) which bypasses
theme/priority resolution; wrap _props with useComponentProps("CommandPalette",
_props) and replace uses of _props (searchDelay, labelKey, and the other
occurrences around the fuse setup) with the resolved props (props.searchDelay,
props.labelKey) so debounce timing and highlight generation honor UTheme and
other prop priority sources; update all instances mentioned (including the reads
at the later 355–356 region) to use the resolved props.

In `@src/runtime/components/PinInput.vue`:
- Line 77: Wrap the raw _props with useComponentProps and pass the resolved
props into useFormField so theme/inherited values flow correctly: call const
resolved = useComponentProps('pinInput', _props) (or appropriate component name
used in your UI config) and replace useFormField<PinInputProps>(_props) with
useFormField<PinInputProps>(resolved) so color, size, highlight, id, name,
disabled, etc. come from the full priority chain.

---

Outside diff comments:
In `@src/runtime/components/content/ContentSearch.vue`:
- Around line 248-253: The shortcut registration uses raw _props instead of
resolved props, so change the code to wrap incoming _props with
useComponentProps('ContentSearch', _props) and use that resolvedProps.shortcut
when calling defineShortcuts; specifically, create const resolvedProps =
useComponentProps('ContentSearch', _props) near the top of the setup and replace
_props.shortcut with resolvedProps.shortcut in the defineShortcuts handler
(keeping the existing handler logic that toggles open.value).
- Around line 102-130: Replace the incorrect import of useForwardProps from
'reka-ui' with the repo-local composable and use it to build commandPaletteProps
and modalProps so proxy-injected/theme defaults (e.g., fullscreen, close,
searchDelay) are preserved; locate where commandPaletteProps and modalProps are
created and switch the imported symbol to ../composables/useForwardProps. Also
update defineShortcuts (or the place where shortcuts are derived) to read
props.shortcut instead of _props.shortcut so the theme/app/default resolution
via useComponentProps is respected.

In `@src/runtime/components/DashboardSidebar.vue`:
- Around line 72-84: The runtime default for the mode prop in the
withDefaults(defineProps<DashboardSidebarProps<T>>()) block is incorrect—change
the value from 'slideover' to 'modal' to match the documented API, and
add/update the JSDoc `@defaultValue` 'modal' on the DashboardSidebarProps<T> mode
prop declaration so docs and runtime defaults stay in sync.

In `@src/runtime/components/EditorToolbar.vue`:
- Around line 132-138: The computed Component currently reads layout directly
from _props (bypassing the props resolution chain), so change it to first wrap
raw props using useComponentProps with this component's name (e.g.,
useComponentProps('EditorToolbar', _props)) and then read layout from the
resolved props; update the Component computed to use resolvedProps.layout
instead of _props.layout so themed/defaulted values from useComponentProps are
honored (refer to the computed named Component, the raw _props, and the
useComponentProps helper).

In `@src/runtime/components/InputTags.vue`:
- Around line 93-116: The component is calling useFormField and useFieldGroup
with raw _props so theme/default fallbacks from useComponentProps('inputTags',
_props) are bypassed; replace the _props arguments with the resolved props
variable and ensure the ui and inputSize computed use the resolved values (e.g.,
use inputSize.value || props.size, color.value || props.color, highlight.value
?? props.highlight, disabled.value ?? props.disabled) so theme defaults from
useComponentProps take effect; update calls to useFormField(_props) ->
useFormField(props) and useFieldGroup(_props) -> useFieldGroup(props) and adjust
the ui computed to fall back to props.* where the form-field/group composables
return undefined.
🪄 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: 2243f452-be06-457f-a74c-c6f4cdf57167

📥 Commits

Reviewing files that changed from the base of the PR and between 52fce9c and 4d138d4.

📒 Files selected for processing (20)
  • eslint.config.mjs
  • src/runtime/components/Accordion.vue
  • src/runtime/components/Badge.vue
  • src/runtime/components/Button.vue
  • src/runtime/components/ChatMessage.vue
  • src/runtime/components/ChatPrompt.vue
  • src/runtime/components/ChatReasoning.vue
  • src/runtime/components/ChatTool.vue
  • src/runtime/components/ColorPicker.vue
  • src/runtime/components/CommandPalette.vue
  • src/runtime/components/DashboardPanel.vue
  • src/runtime/components/DashboardSearch.vue
  • src/runtime/components/DashboardSidebar.vue
  • src/runtime/components/EditorToolbar.vue
  • src/runtime/components/InputNumber.vue
  • src/runtime/components/InputTags.vue
  • src/runtime/components/PinInput.vue
  • src/runtime/components/Tabs.vue
  • src/runtime/components/Tree.vue
  • src/runtime/components/content/ContentSearch.vue

Comment thread src/runtime/components/Button.vue
Comment thread src/runtime/components/ChatPrompt.vue Outdated
Comment thread src/runtime/components/ColorPicker.vue
Comment thread src/runtime/components/CommandPalette.vue Outdated
Comment thread src/runtime/components/PinInput.vue
A handful of components were still reading `_props.X` (raw) inside their
`<script>` setup. Because the `nuxt-ui/no-bare-prop-refs` ESLint rule
only walks templates, these silently bypassed the `useComponentProps`
proxy and never picked up `<UTheme :props>` defaults:

- `CommandPalette`: `searchDelay`, `labelKey` (×2) — fuzzy-search debounce
  and highlight pipeline now honor theme defaults.
- `ContentSearch`: `shortcut` — `defineShortcuts` key now flows through
  the proxy.
- `EditorToolbar`: `layout` — picks bubble/floating/fixed via theme.
- `Form`: `loadingAuto`.
- `Textarea`: `rows` (×2).

The components above pass an explicit generic to `useComponentProps`
(`useComponentProps<XProps<T>>(...)`) which drops the `withDefaults`
narrowing, so TS sees `T['X'] | undefined`. The proxy guarantees the
resolved value is non-undefined whenever `withDefaults` provides a
default, hence the `!` non-null assertions.

Also:
- `Button` and `ContentSearch` now import `useForwardProps` from the
  local proxy-aware composable. Reka-UI's version filters by
  `vnode.props ∪ vm.type.props.<key>.default` and was silently dropping
  theme-supplied `to`/`href`/`target` (Button) and `fullscreen`/`close`
  /`searchDelay` (ContentSearch). Boolean leak risk no longer applies
  since the proxy gates the `withDefaults` fallback.
- `ChatPrompt`: `props.placeholder || t(...)` → `??` so an explicit
  empty string isn't replaced by the i18n fallback.
- `ColorPicker`: add `@defaultValue` JSDoc for `throttle` and
  `defaultValue` to match `withDefaults`.
- `useComponentProps`: add a JSDoc note clarifying that `ownKeys` /
  `has` only enumerate the underlying schema (theme defaults aren't
  enumerable) — the contract our local `useForwardProps` relies on.
- `ThemeDefaults['form']`: add a `// TODO` explaining the `<any, any,
  any>` is intentional (state/schema/fields generics aren't themable).
- Carousel: switch `_props.autoplay` / `_props.autoScroll` to `props.X`
  with optional chaining so the autoplay/autoScroll plugins honor
  `<UTheme :props>` defaults. Adds `?.stopOnInteraction ?? true` which
  also hardens the path against an undefined value reaching `.stopOnInteraction`.
- DashboardSidebar: JSDoc said `@defaultValue 'modal'` but
  `withDefaults` and the published docs both say `'slideover'`. Update
  the JSDoc to match runtime, not the other way around — flipping the
  runtime would silently change the default mobile menu shape for every
  consumer.
`_props.level` (×6 in the template) bypassed the `useComponentProps`
proxy. The `nuxt-ui/no-bare-prop-refs` ESLint rule explicitly skips
identifiers starting with `_`, so this slipped past the autofixer.
Switch all reads to `props.level!` for consistency with the rest of the
migration — `level` has a `withDefaults` default of `0`, so the
non-null assertion is runtime-safe.
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.

🧹 Nitpick comments (2)
src/runtime/composables/useComponentProps.ts (2)

260-266: Clarify the distinction between propDef.default and withDefaults in the comment.

The comment on lines 255-259 mentions withDefaults, but the check on line 261 looks for propDef.default. While withDefaults does populate propDef.default at runtime, this subtlety could confuse future maintainers. Consider refining the comment to explicitly note that withDefaults sets propDef.default under the hood.

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

In `@src/runtime/composables/useComponentProps.ts` around lines 260 - 266, Update
the comment in useComponentProps above the check that reads propDef.default to
explicitly state that Vue's withDefaults (used when defining components)
populates the runtime prop definition's default field, so the code correctly
checks (vm?.type as any)?.props?.[prop] and its own 'default' property; mention
both symbols: propDef and withDefaults, and clarify that the runtime check for
propDef.default is effectively detecting defaults provided via withDefaults
rather than only static prop options.

203-207: propIsDefined may miss explicitly-passed undefined values.

The check vnode.props[...] !== undefined cannot distinguish between "prop not passed" and "prop explicitly passed as undefined". While rare, if a consumer writes :color="undefined" expecting to clear a theme default, this check will treat it as "not defined" and fall through to theme defaults.

This is a known limitation of Vue's vnode inspection and may be acceptable, but worth documenting in the JSDoc.

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

In `@src/runtime/composables/useComponentProps.ts` around lines 203 - 207, Update
the JSDoc for the function propIsDefined to document that vnode.props inspection
cannot distinguish a prop that was not passed from one explicitly passed as
undefined; reference the function name propIsDefined and the use of
vnode.props[camelCase(prop)] / vnode.props[kebabCase(prop)] in the note, explain
that an explicitly-passed undefined value will be treated as "not defined" by
this check and recommend workarounds (e.g., pass null or an explicit flag) for
callers who need to clear defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/composables/useComponentProps.ts`:
- Around line 260-266: Update the comment in useComponentProps above the check
that reads propDef.default to explicitly state that Vue's withDefaults (used
when defining components) populates the runtime prop definition's default field,
so the code correctly checks (vm?.type as any)?.props?.[prop] and its own
'default' property; mention both symbols: propDef and withDefaults, and clarify
that the runtime check for propDef.default is effectively detecting defaults
provided via withDefaults rather than only static prop options.
- Around line 203-207: Update the JSDoc for the function propIsDefined to
document that vnode.props inspection cannot distinguish a prop that was not
passed from one explicitly passed as undefined; reference the function name
propIsDefined and the use of vnode.props[camelCase(prop)] /
vnode.props[kebabCase(prop)] in the note, explain that an explicitly-passed
undefined value will be treated as "not defined" by this check and recommend
workarounds (e.g., pass null or an explicit flag) for callers who need to clear
defaults.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e5c1821a-8c39-4f39-87b2-f4d572f68e0d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d138d4 and c47bac7.

📒 Files selected for processing (12)
  • src/runtime/components/Button.vue
  • src/runtime/components/Carousel.vue
  • src/runtime/components/ChatPrompt.vue
  • src/runtime/components/ColorPicker.vue
  • src/runtime/components/CommandPalette.vue
  • src/runtime/components/DashboardSidebar.vue
  • src/runtime/components/EditorToolbar.vue
  • src/runtime/components/Form.vue
  • src/runtime/components/Textarea.vue
  • src/runtime/components/content/ContentNavigation.vue
  • src/runtime/components/content/ContentSearch.vue
  • src/runtime/composables/useComponentProps.ts
✅ Files skipped from review due to trivial changes (1)
  • src/runtime/components/ChatPrompt.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/runtime/components/ColorPicker.vue

Comment thread src/runtime/components/Avatar.vue
Add proxy fallback at the tv() call site so theme size/color/highlight
apply when no closer context (UFormField, UFieldGroup, UAvatarGroup)
wraps the component:

  size: size.value ?? props.size,
  color: color.value ?? props.color,
  highlight: highlight.value ?? props.highlight

Final precedence: explicit > closer-context > <UTheme :props> > withDefaults
> app.config > tv defaults.
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.

UTheme Support for props as well Tooltip: Add arrow prop to app.vue level props Override default props from AppConfig

5 participants