--- title: Component Fallthrough Attributes Best Practices impact: MEDIUM impactDescription: Incorrect $attrs access and reactivity assumptions can cause undefined values and watchers that never run type: best-practice tags: [vue3, attrs, fallthrough-attributes, composition-api, reactivity] --- # Component Fallthrough Attributes Best Practices **Impact: MEDIUM** - Fallthrough attributes are straightforward once you follow Vue's conventions: hyphenated names use bracket notation, listener keys are camelCase `onX`, and `useAttrs()` is current-but-not-reactive. ## Task List - Access hyphenated attribute names with bracket notation (for example `attrs['data-testid']`) - Access event listeners with camelCase `onX` keys (for example `attrs.onClick`) - Do not `watch()` values returned from `useAttrs()`; those watchers do not trigger on attr changes - Use `onUpdated()` for attr-driven side effects - Promote frequently observed attrs to props when reactive observation is required ## Access Attribute and Listener Keys Correctly Hyphenated attribute names preserve their original casing in JavaScript, so dot notation does not work for keys that include `-`. **BAD:** ```vue ``` **GOOD:** ```vue ``` ### Naming Reference | Parent Usage | Access in `attrs` | |--------------|-------------------| | `class="foo"` | `attrs.class` | | `data-id="123"` | `attrs['data-id']` | | `aria-label="..."` | `attrs['aria-label']` | | `foo-bar="baz"` | `attrs['foo-bar']` | | `@click="fn"` | `attrs.onClick` | | `@custom-event="fn"` | `attrs.onCustomEvent` | | `@update:modelValue="fn"` | `attrs['onUpdate:modelValue']` | ## `useAttrs()` Is Not Reactive `useAttrs()` always reflects the latest values, but it is intentionally not reactive for watcher tracking. **BAD:** ```vue ``` **GOOD:** ```vue ``` **GOOD:** ```vue ``` ## Common Patterns ### Check for optional attrs safely ```vue ``` ### Forward listeners after internal logic ```vue ``` ## TypeScript Notes `useAttrs()` is typed as `Record`, so cast individual keys when needed. ```vue ```