You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The same `?? props.X` pattern applies to `useAvatarGroup` (`size`) and any other context composable whose contract is `props?.x ?? injected.x`. The composable itself stays untouched — the fallback lives at the `tv()` call site so the wrapper-vs-theme precedence is explicit and reviewable.
240
+
211
241
## Components with Icons
212
242
213
243
```vue
@@ -235,43 +265,35 @@ defineExpose({
235
265
</script>
236
266
```
237
267
238
-
## Resolving Variants in Template Logic
268
+
## Theme Defaults
239
269
240
-
`tv()`'s `defaultVariants` only apply when computing CSS classes — they do **not** affect runtime checks (e.g. `<component :is>`, `v-if`, computed conditionals). When a variant drives template logic, use `useResolvedVariants` to mirror `tv()`'s resolution: **prop > `app.config.ts``defaultVariants` > fallback**.
270
+
`useComponentProps` is the primary integration with `<UTheme>`. The proxy resolves the priority chain **explicit prop > nearest `<UTheme :props>` > `withDefaults` > `app.config.ui.<name>.defaultVariants`** for every prop — including ones driving template logic that `tv().defaultVariants` can't reach (`<component :is>`, `v-if`, computed conditionals). `theme.defaultVariants` is intentionally NOT in the proxy chain — it only feeds `tv()` class resolution. If a prop value is consumed in template logic, it must come from one of the proxy-resolved sources (typically `withDefaults`):
241
271
242
272
```vue
243
-
<script setup lang="ts">
244
-
import { useResolvedVariants } from '../composables/useResolvedVariants'
For nested prop paths (e.g. `props.content?.position`), use the `overrides` parameter:
257
-
258
-
```ts
259
-
const { position } =useResolvedVariants('select', props, theme, ['position'], {
260
-
position: () =>props.content?.position
261
-
})
262
-
```
278
+
Notes:
279
+
- The proxy passes through to `_props` for explicitly set props, so `withDefaults` fallbacks stay lower priority than `<UTheme>` overrides.
280
+
- The `ui` prop is deep-merged (slot classes layered on top of theme overrides). All other props are explicit-wins.
281
+
-**Always read props as `props.x` in templates and `<script setup>`.** Bare prop names (`{{ label }}`, `v-if="arrow"`) resolve to `_props` and bypass the proxy, so `<UTheme :props>` defaults won't apply. The `nuxt-ui/no-bare-prop-refs` ESLint rule autofixes this.
282
+
- Pass the **raw**`_props` (not the proxy) to context composables — `useFormField`, `useFieldGroup`, `useAvatarGroup`. Their internal fallback is `props?.x ?? injected.x`, so the wrapping `<UFormField>` / `<UFieldGroup>` / `<UAvatarGroup>` should beat `<UTheme :props>` / `withDefaults` / `app.config` defaults (closer context wins). **Then always fall back to the proxy in `tv()` calls** — `size: formSize.value ?? props.size`, `color: color.value ?? props.color`, `highlight: highlight.value ?? props.highlight`. Without `?? props.X`, `<UTheme :props>` is silently dropped when no closer context wraps the component. Final chain: `explicit > closer-context > UTheme > withDefaults > app.config > tv defaults`. `useComponentIcons` has no injection chain, so pass the proxy `props` directly.
283
+
- Reka primitives' `useForwardProps` / `useForwardPropsEmits` filter root props by `vm.vnode.props ∪ withDefaults` and would strip theme-supplied values. Import `useForwardProps` from `composables/useForwardProps.ts` instead — same `(source, emits?)` signature, proxy-aware.
263
284
264
285
## Key Patterns
265
286
266
287
| Pattern | Usage |
267
288
|---------|-------|
289
+
|`useComponentProps(name, _props)`| Theme-aware proxy — default for new components |
290
+
|`useForwardProps(source, emits?)` (local) | Forward Reka UI props/emits without filtering theme defaults |
268
291
|`withDefaults`| Runtime default values |
269
292
|`defineOptions({ inheritAttrs: false })`| When spreading `$attrs` to inner element |
270
-
|`reactivePick`+ `useForwardPropsEmits`| Forward Reka UI props/emits|
293
+
|`reactivePick`| Pick keys off `props` (the proxy) before forwarding|
@@ -280,3 +302,16 @@ Add to `src/runtime/types/index.ts`:
280
302
```ts
281
303
export*from'../components/ComponentName.vue'
282
304
```
305
+
306
+
## Register in `ThemeDefaults`
307
+
308
+
The `ThemeDefaults` interface in `src/runtime/composables/useComponentProps.ts` powers autocomplete inside `<UTheme :props="{ componentName: { … } }">`. The CLI scaffolder (`nuxt-ui make component`) auto-inserts the entry; only do this manually if you skipped the CLI:
The key is the component name in camelCase (matches the `#build/ui` registry). The value is `Partial<XProps>`. This is a flat literal interface (not a mapped type) because Volar only surfaces inner-prop autocomplete for interface members, not mapped-type members, in template inline objects.
Copy file name to clipboardExpand all lines: AGENTS.md
+10-9Lines changed: 10 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -90,11 +90,11 @@ Load these based on your task. **Do not load all files at once** — only load w
90
90
| Props defaults | Use `withDefaults()` for runtime, JSDoc `@defaultValue` for docs |
91
91
| Template slots | Add `data-slot="name"` attributes on all elements |
92
92
| Computed ui | Always use `computed(() => tv(...))` for reactive theming |
93
-
| Theme support | Use `useComponentUI(name, props)` to merge Theme context with component `ui` prop |
93
+
| Theme defaults | Wrap raw props with `useComponentProps(name, _props)` to resolve the priority chain (explicit prop > `<UTheme :props>` > `withDefaults` > `app.config.ui.<name>.defaultVariants`). The proxy deep-merges `ui` automatically — read `props.ui?.<slot>` in templates. `theme.defaultVariants` is **not** read by the proxy — it only feeds `tv()` class resolution. Pass the **raw**`_props` (not the proxy) to `useFormField` / `useFieldGroup` / `useAvatarGroup` so their injection precedence (closer context wins) stays correct. |
94
+
| Form/group fallback | When consuming `size` / `color` / `highlight` from `useFormField`, `useFieldGroup`, or `useAvatarGroup`, always fall back to the proxy in `tv()` calls: `size: size.value ?? props.size`, `color: color.value ?? props.color`, `highlight: highlight.value ?? props.highlight`. This gives the full precedence `explicit > group/formField > <UTheme :props> > undefined`. Without the `?? props.X` fallback, `<UTheme :props>` is silently dropped when the closer context (FormField/FieldGroup/AvatarGroup) is absent. |
94
95
| Semantic colors | Use `text-default`, `bg-elevated`, etc. - never Tailwind palette |
95
-
| Reka UI props | Use `reactivePick` + `useForwardPropsEmits`to forward props |
96
+
| Reka UI props | Use `reactivePick` + `useForwardProps(source, emits?)` from `composables/useForwardProps`to forward props (proxy-aware; reka-ui's `useForwardProps` / `useForwardPropsEmits` filter out `<UTheme :props>` defaults)|
96
97
| Form components | Use `useFormField` and `useFieldGroup` composables |
97
-
| Variant in template logic | Use `useResolvedVariants(name, props, theme, ['variant'])` when variant values are consumed in template logic (`<component :is>`, `v-if`, computed) — `tv()``defaultVariants` only affect classes, not runtime checks |
98
98
99
99
## Component Creation Workflow
100
100
@@ -107,12 +107,13 @@ Progress:
107
107
- [ ] 2. Implement component in src/runtime/components/
108
108
- [ ] 3. Create theme in src/theme/
109
109
- [ ] 4. Export types from src/runtime/types/index.ts
110
-
- [ ] 5. Write tests in test/components/
111
-
- [ ] 6. Create docs in docs/content/docs/2.components/
112
-
- [ ] 7. Add playground page
113
-
- [ ] 8. Run pnpm run lint
114
-
- [ ] 9. Run pnpm run typecheck
115
-
- [ ] 10. Run pnpm run test
110
+
- [ ] 5. Register in ThemeDefaults interface (src/runtime/composables/useComponentProps.ts)
111
+
- [ ] 6. Write tests in test/components/
112
+
- [ ] 7. Create docs in docs/content/docs/2.components/
0 commit comments