Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.0 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Use kebab-case for Event Listeners in Templates | LOW | Vue auto-converts camelCase emits to kebab-case listeners but consistency improves readability | best-practice |
|
Use kebab-case for Event Listeners in Templates
Impact: LOW - Vue automatically converts event names between camelCase and kebab-case. You can emit in camelCase (emit('someEvent')) and listen with kebab-case (@some-event). However, following consistent conventions improves code readability and matches HTML attribute conventions.
Task Checklist
- Emit events using camelCase in JavaScript:
emit('updateValue') - Listen to events using kebab-case in templates:
@update-value - Be consistent across your codebase
- Understand Vue's automatic case conversion
The Convention
Recommended pattern:
<!-- ChildComponent.vue -->
<script setup>
const emit = defineEmits(['updateValue', 'itemSelected', 'formSubmit'])
function handleChange(value) {
// Emit in camelCase (JavaScript convention)
emit('updateValue', value)
}
function selectItem(item) {
emit('itemSelected', item)
}
</script>
<!-- ParentComponent.vue -->
<template>
<!-- Listen in kebab-case (HTML attribute convention) -->
<ChildComponent
@update-value="handleUpdate"
@item-selected="handleSelect"
@form-submit="handleSubmit"
/>
</template>
Vue's Automatic Conversion
Vue handles these automatically in template syntax only:
| Emitted (camelCase) | Listener (kebab-case) | Works? |
|---|---|---|
emit('updateValue') |
@update-value |
Yes |
emit('itemSelected') |
@item-selected |
Yes |
emit('formSubmit') |
@form-submit |
Yes |
<!-- All of these work equivalently in Vue 3 templates -->
<Child @updateValue="handler" /> <!-- camelCase listener -->
<Child @update-value="handler" /> <!-- kebab-case listener (preferred) -->
Important: Template-Only Behavior
This auto-conversion only works in template syntax (@event-name). It does NOT work in render functions or programmatic event listeners:
// In render functions, use camelCase with 'on' prefix
import { h } from 'vue'
// CORRECT - camelCase event name with 'on' prefix
h(ChildComponent, {
onUpdateValue: (value) => handleUpdate(value),
onItemSelected: (item) => handleSelect(item)
})
// WRONG - kebab-case does NOT work in render functions
h(ChildComponent, {
'onUpdate-value': (value) => handleUpdate(value), // Won't work!
'on-update-value': (value) => handleUpdate(value) // Won't work!
})
// Programmatic listeners also require camelCase
import { ref, onMounted } from 'vue'
const childRef = ref<ComponentPublicInstance | null>(null)
onMounted(() => {
// CORRECT - camelCase
childRef.value?.$on?.('updateValue', handler)
// WRONG - kebab-case won't match
childRef.value?.$on?.('update-value', handler) // Won't work!
})
Summary:
- Templates: Auto-conversion works (
@update-valuematchesemit('updateValue')) - Render functions: Must use
onEventNameformat (camelCase withonprefix) - Programmatic listeners: Must use the exact emitted event name (typically camelCase)
Why kebab-case in Templates?
- HTML convention: HTML attributes are case-insensitive and traditionally kebab-case
- Consistency with props: Props follow the same pattern (
props.userName->user-name="...") - Readability:
@user-profile-updatedis easier to read than@userProfileUpdated - Vue style guide: Vue's official style guide recommends this pattern
TypeScript Declarations
When using TypeScript, define emits in camelCase:
<script setup lang="ts">
const emit = defineEmits<{
updateValue: [value: string] // camelCase
itemSelected: [item: Item] // camelCase
'update:modelValue': [value: string] // Special v-model syntax (with colon)
}>()
</script>
v-model Events
For v-model, the update: prefix uses a colon, not kebab-case:
<script setup>
// Correct: colon separator for v-model updates
const emit = defineEmits(['update:modelValue', 'update:firstName'])
function updateValue(newValue) {
emit('update:modelValue', newValue)
}
</script>
<!-- Parent - v-model handles the event automatically -->
<CustomInput v-model="value" />
<CustomInput v-model:first-name="firstName" />
Vue 2 Difference
In Vue 2, event names did NOT have automatic case conversion. This caused issues:
// Vue 2 - camelCase events couldn't be listened to in templates
this.$emit('updateValue') // Emitted as 'updateValue'
// Template converts to lowercase
<child @updatevalue="handler"> // Listened as 'updatevalue' - NO MATCH!
Vue 3 fixed this with automatic camelCase-to-kebab-case conversion.