Skip to content

Instantly share code, notes, and snippets.

@danielkellyio
Created September 18, 2025 13:39
Show Gist options
  • Select an option

  • Save danielkellyio/cd1d3261b8c6a39fd02d97446a6bb209 to your computer and use it in GitHub Desktop.

Select an option

Save danielkellyio/cd1d3261b8c6a39fd02d97446a6bb209 to your computer and use it in GitHub Desktop.
Vue Cursor Rules
---
globs: **/*.vue,**/*.ts,**/*.js
alwaysApply: false
---
# Vue 3 Best Practices & Rules
## General Rules
ALWAYS use the Composition API with `<script setup>` syntax.
ALWAYS use Typescript.
ALWAYS type props, emits, and model values.
NEVER use any. Prefer unknown and narrow types explicitly.
Avoid redundant refs: if you can derive state with computed, don't store extra refs.
## Code Style
Use early returns whenever possible to make the code more readable.
ALWAYS follow the single responsibility principle:
- Components handle rendering and presentation only. Move logic, state, and side effects into composables.
- Composables handle logic and state only. Do not include rendering or DOM concerns.
- Utilities handle pure, stateless functions only. Do not use Vue reactivity here.
- For small, single-use composables, declare them inline in a component's <script setup> block.
## Components
### Structure
The sections should be in the following order, and only include the sections that are needed:
- <script setup lang="ts">
- <template>
- <style scoped>
In the <script setup> section, follow @general-ts.mdc rules.
### Naming
ALWAYS use PascalCase for component names.
### Best Practices
ALWAYS use camelCase in <script setup> for props and emits
ALWAYS use kebab-case in templates for props and emits
ALWAYS use useTemplateRef('refName') to use refs in templates.
ALWAYS use defineModel<type>() to define v-model bindings. Do not use modelValue prop and update:modelValue event manually
Prefer built-in event modifiers @click.stop and @click.prevent over manual event.stopPropagation() and event.preventDefault() in script.
### Using defineEmits
ALWAYS define component emits with `defineEmits<{ camelCaseName: [argOne: string, argTwo: number] }>()`
Use `const emit =` ONLY if emits are used in the script block
Inside templates, use `$emit('event', argOne)` to emit events
### Props
ALWAYS define props with defineProps<{ camelCaseName: string }>()
Only add `const props =` when props are used in the <script> block
ALWAYS destructure props to declare default values. Do not use withDefaults
```ts
// Good
defineProps<{
myProp: number
}>()
// Bad: props is not used in the script block
const props = defineProps<{
myProp: number
}>()
```
```ts
// Good: props is used in the script block
const props = defineProps<{
myProp: number
}>()
console.log(props.myProp)
```
```ts
// Good: default value is assigned
const { myProp = 0 } = defineProps<{
propOne?: number
}>()
console.log(myProp)
```
### Watching Props Requires a Getter Function
ALWAYS use a getter function when watching props.
```ts
// Bad
watch(props.propOne, (newVal) => {
console.log(newVal)
})
// Good
watch(
() => props.propOne,
(newVal) => {
console.log(newVal)
},
)
```
## Composables
### Naming
ALWAYS use camelCase for composable names.
ALWAYS start the name with `use` prefix.
### Best Practices
Prefer using `MaybeRefOrGetter` and `toValue` when passing reactive data to composables.
Prefer using objects as function arguments instead of multiple arguments.
Example:
```ts
// Good
myFunction({ arg1: 'value1', arg2: 'value2' })
// Bad
myFunction('value1', 'value2')
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment