---
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
```