vue3-standards
Skill428 starsChanged 10 days agoMITView on GitHub
Build, refactor, and review Vue 3.5 single-file components with TypeScript, Composition API, modern compiler macros, typed reactivity, template refs, models, props, emits, slots, watchers, and consistent template naming. Use for any TeyvatGuide .vue change, new Vue component, Vue composable integration, legacy Vue syntax modernization, or Vue code review; apply together with the repository TypeScript and UI skills when those concerns are also present.
We split this skill into the rules it gives an agent
28 rules. About 0 are common, 4 are distinctive, and 24 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
24
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use Vue 3.5 features supported by the repository.
- Very specific: line 15
Use Composition API and <script setup> for new code.
- Distinctive: line 17
Use PascalCase component imports and tags.
- Very specific: line 59
Keep const props = defineProps<Props>() when the whole read-only props object must be forwarded or inspected.
- Very specific: line 60
Use props.foo in script, while templates may reference declared props directly.
- Very specific: line 61
When watching a destructured prop, pass a getter: watch(() => pageSize, callback).
- Very specific: line 67
Use defineOptions() for inheritAttrs, component names, or custom options.
- Very specific: line 69
Use <script lang="ts" setup generic="T extends ..."> for truly reusable generic components instead of weakening types with unknown or any.
- Very specific: line 76
Use named models for multiple bindings: defineModel<string>("firstName", { required: true }), consumed as v-model:firstName by the parent.
- Very specific: line 80
Prefer { required: true } when the parent must own the value.
- Very specific: line 82
Use the runtime useModel(props, key) helper only in non-SFC components or raw setup() implementations.
- Very specific: line 87
Write explicit generic contracts for component state and derived business values: ref<T>(), shallowRef<T>(), and computed<T>().
- Very specific: line 93
Do not destructure ordinary reactive objects because that disconnects primitive properties from reactivity.
- Very specific: line 98
Use import type for types.
- Very specific: line 103
Use useTemplateRef<T>("key") with a matching ref="key".
- Distinctive: line 109
Prefer declarative rendering to direct DOM mutation.
- Very specific: line 120
Prefer : and @ directive shorthands.
- Very specific: line 131
Keep an explicit expression when names differ, a transformation is required, or the value is not directly in template scope: …
- Very specific: line 133
Use method handlers for non-trivial behavior: @save-success="handleSaveSuccess".
- Very specific: line 138
Use computed() for pure derived state and watch() / watchEffect() only for side effects.
- Distinctive: line 141
Create watchers synchronously during setup so Vue stops them automatically.
- Very specific: line 145
Use useId() for SSR-stable, per-application form and accessibility IDs.
- Distinctive: line 147
Treat props as read-only. Request changes with an emit or model instead of mutating nested parent state.
- Very specific: line 148
Use a stable domain key for v-for.
- Very specific: line 151
Avoid legacy .sync, filters, event buses, $refs, Reactivity Transform macros, and broad getCurrentInstance() access.
- Very specific: line 156
Run pnpm lint-vue for Vue and TypeScript type safety.
- Very specific: line 157
Run pnpm lint:code for script and template conventions.
- Very specific: line 158
Run pnpm lint:style when Vue styles changed.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Vue 3 Modern Standards
Use Vue 3.5 features supported by the repository. Inspect package.json, nearby components, ESLint, Prettier,
Stylelint, and project instructions before editing; treat them as authoritative when they are stricter than this skill.
Structure every SFC consistently
- Order blocks as
<template>,<script lang="ts" setup>,<style lang="scss" scoped>. - Omit an unused style block. Keep styles scoped unless a global style is explicitly required.
- Use Composition API and
<script setup>for new code. Do not add Options API, mixins,this, or a second<script>block when a compiler macro expresses the same behavior. - Use PascalCase component imports and tags. Follow the repository's existing file naming convention.
<template>
<ResultPanel :userName :normalizedQuery @retry-request="handleRetryRequest" />
<input ref="searchInput" v-model="query" />
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, useTemplateRef } from "vue";
type SearchPanelProps = {
userName: string;
};
const { userName } = defineProps<SearchPanelProps>();
const query = defineModel<string>({ required: true });
const searchInput = useTemplateRef<HTMLInputElement>("searchInput");
const pending = ref<boolean>(false);
const normalizedQuery = computed<string>(() => query.value.trim());
function handleRetryRequest(): void {
pending.value = true;
}
onMounted(() => searchInput.value?.focus());
</script>
<style lang="scss" scoped>
.search-panel {
display: grid;
}
</style>Declare component contracts with compiler macros
- Declare props with
defineProps<Props>(); use atype, not aninterface, under this repository's TypeScript rules. Do not import compiler macros. - On Vue 3.5, prefer reactive props destructuring with native defaults when only selected props are needed:
const { title, pageSize = 20 } = defineProps<Props>(). The destructured bindings remain reactive inside the same<script setup>block. - Keep
const props = defineProps<Props>()when the whole read-only props object must be forwarded or inspected. Useprops.fooin script, while templates may reference declared props directly. - When watching a destructured prop, pass a getter:
watch(() => pageSize, callback). Do not pass its current primitive value as the source. - Declare emits with typed named tuples, for example
defineEmits<{ saveSuccess: [recordId: number]; close: [] }>(). - Declare typed slots with
defineSlots<{ default(props: SlotProps): unknown }>()when slot props form a public contract. Prefer it touseSlots()for TypeScript-based slot declarations. - Use
defineOptions()forinheritAttrs, component names, or custom options. UsedefineExpose()only for the smallest parent-facing imperative API;<script setup>components are closed by default. - Use
<script lang="ts" setup generic="T extends ...">for truly reusable generic components instead of weakening types withunknownorany.
Use modern model APIs correctly
- In an SFC using
<script setup>, preferdefineModel<T>()over manually pairingmodelValuewithupdate:modelValue, or building a writablecomputed()proxy. - Use named models for multiple bindings:
defineModel<string>("firstName", { required: true }), consumed asv-model:firstNameby the parent. - Specify value and modifier types when modifiers are supported:
defineModel<string, "trim" | "uppercase">(). - Prefer
{ required: true }when the parent must own the value. Be cautious with model defaults: a child default can desynchronize from an omitted,undefinedparent ref. - Use the runtime
useModel(props, key)helper only in non-SFC components or rawsetup()implementations.useModel()is the lower-level helper behinddefineModel()and requires explicit props and emits declarations.
Type reactivity explicitly
- Write explicit generic contracts for component state and derived business values:
ref<T>(),shallowRef<T>(), andcomputed<T>(). Includenullorundefinedin the type when it is a real lifecycle state. - Type writable computed values with
computed<T>({ get, set }). - Let
reactive()infer from its initializer. Do not passreactive<T>(): Vue's deep ref-unwrapped return type can differ fromT. Add a variable annotation only when a wider contract is needed. - Do not destructure ordinary reactive objects because that disconnects primitive properties from reactivity. Use
toRef()/toRefs()when independent refs are required. Vue 3.5 reactive props destructuring is the compiler- supported exception. - Type native DOM event parameters, such as
function handleInput(event: Event): void, and narrowevent.targetbefore reading element-specific fields. - Use
import typefor types. Preserve precise unions and avoidany, non-null assertions, and type assertions when narrowing or a generic can express the contract.
Prefer useTemplateRef() for template refs
- Use
useTemplateRef<T>("key")with a matchingref="key"; do not create template element refs withref<T | null>(null)and name matching. - Type DOM refs with the exact element type, such as
HTMLInputElement. For component refs useInstanceType<typeof ChildComponent>when inference is insufficient, orComponentPublicInstancewhen only common instance members are needed. - Access a template ref only after mount and account for
null, including unmounts caused byv-if. - Prefer declarative rendering to direct DOM mutation. Use a template ref only for focus, measurement, integration with imperative libraries, or a deliberately exposed child method.
Keep naming consistent across script and template
- Declare multi-word props, models, variables, and event identifiers in camelCase:
userName,modelValue,saveSuccess. - Bind multi-word component props and named model arguments in SFC templates with the same camelCase spelling as
their declarations:
:userName,:modelValue,v-model:firstName. Do not convert them to kebab-case. - Listen to multi-word component events in templates with kebab-case:
@save-success. Use PascalCase for component tags. - Prefer
:and@directive shorthands. When a bound prop/attribute and its in-scope value have the same name, use Vue 3.4+ same-name shorthand:
<!-- Avoid -->
<UserCard :userName="props.userName" :loading="loading" />
<!-- Prefer; declared props are directly visible to the template -->
<UserCard :userName :loading />- Keep an explicit expression when names differ, a transformation is required, or the value is not directly in
template scope:
:userName="owner.displayName". - Use method handlers for non-trivial behavior:
@save-success="handleSaveSuccess". Keep inline handlers limited to short assignments or direct calls.
Apply modern reactivity and rendering patterns
- Use
computed()for pure derived state andwatch()/watchEffect()only for side effects. - Register cleanup for stale async watcher work. On Vue 3.5, call
onWatcherCleanup()synchronously before anyawait; otherwise use the callback-providedonCleanupfunction. - Create watchers synchronously during setup so Vue stops them automatically. If a watcher must be created later, retain and call its stop handle.
- In composables that accept values, refs, or getters, use
MaybeRefOrGetter<T>withtoValue()at the tracking point instead of manually branching on input kinds. - Use
useId()for SSR-stable, per-application form and accessibility IDs. Call it during setup, not inside acomputed()getter. - Treat props as read-only. Request changes with an emit or model instead of mutating nested parent state.
- Use a stable domain key for
v-for; do not use an array index when list identity can change. Do not putv-ifandv-foron the same element; filter throughcomputed()or move the condition to a wrapper. - Choose
v-showfor frequently toggled content andv-iffor lazy or infrequent branches. - Avoid legacy
.sync, filters, event buses,$refs, Reactivity Transform macros, and broadgetCurrentInstance()access. Modernize them only within the requested scope.
Validate the result
- Run
pnpm lint-vuefor Vue and TypeScript type safety. - Run
pnpm lint:codefor script and template conventions. - Run
pnpm lint:stylewhen Vue styles changed. - Inspect the final diff and preserve unrelated user changes.
Official references
In this repository10 other files it gives its agents.
- AGENTS.mdAGENTS.md
- fluent2-designSkill
- frontend-designSkill
- git-workflowSkill
- sentry-mcpSkill
- tauri-connectorSkill
- testingSkill
- teyvat-guideSkill
- typescript-standardsSkill
- web-design-guidelinesSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

