From frontend
Vue 3 conventions, Composition API patterns, SFC structure, reactivity, composables, and TypeScript integration. Invoke whenever task involves any interaction with Vue code — writing, reviewing, refactoring, debugging, or understanding .vue files, composables, and Vue component architecture.
npx claudepluginhub xobotyi/cc-foundry --plugin frontendThis skill uses the workspace's default tool permissions.
**Composition API is the default. `<script setup>` is the default syntax. If you reach for Options API, you need a
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Composition API is the default. <script setup> is the default syntax. If you reach for Options API, you need a
reason.
Vue 3 rewards explicit, composable code. Prefer ref() over reactive(), composables over mixins, and typed props over
runtime-only validation. References contain extended examples, rationale, and edge cases for each topic.
${CLAUDE_SKILL_DIR}/references/reactivity.md]: Ref unwrapping, watchers, computed edge cases${CLAUDE_SKILL_DIR}/references/sfc.md]: Full compiler macros catalog, scoped styles, template refs${CLAUDE_SKILL_DIR}/references/components.md]: Props, emits, slots, provide/inject${CLAUDE_SKILL_DIR}/references/composables.md]: Design patterns, composition, restrictions${CLAUDE_SKILL_DIR}/references/typescript.md]: Full utility types table, generic components, event
typing${CLAUDE_SKILL_DIR}/references/performance.md]: Update optimization, large lists, profilingref() — Default choice. Works with any value type.reactive() — Grouping related state when destructure is not needed.shallowRef() — Large immutable structures, external state integration.shallowReactive() — Root-level-only reactivity on objects.computed() — Derived state. Caches until dependencies change.ref() Is the Primary APIstring, number, boolean).count.value = newValue)..value access pattern everywhere in script..value in script, omit in template — templates auto-unwrap top-level refs.reactive() Limitationsstate = reactive({...}) breaks tracking.toRefs() if you must destructure.reactive() as the primary primitive. Use ref().{{ count }} works.{{ obj.id + 1 }} breaks if obj.id is a ref. Destructure to
top level to fix.reactive() objects are unwrapped automatically..value.get/set form.watch() vs watchEffect():
Use watch() when | Use watchEffect() when |
|---|---|
| Need old and new values | Don't need old value |
| Want lazy execution | Want immediate execution |
| Watching specific sources | Dependencies are implicit |
| Need conditional watching | Effect tracks all accessed refs |
watch() options:
{ immediate: true } — run callback on creation (like watchEffect).{ deep: true } — watch all nested properties (expensive, use sparingly).watch(() => obj.specificProp, callback).watch([a, b], ([newA, newB]) => {...}).Cleanup: Both watch and watchEffect support cleanup via the onCleanup parameter. Use it for aborting fetch
requests, clearing timers, removing listeners.
Reactive state changes batch DOM updates to the next tick. Use nextTick() for post-DOM-update logic.
Always: <script setup> first, <template> second, <style> last.
<script setup>Order declarations logically:
defineProps, defineEmitsuseRouter(), custom composablesref(), reactive(), computed()watch(), watchEffect()onMounted(), onUnmounted()defineExpose() (rare)Key macros available without import in <script setup>: defineProps(), defineEmits(), defineModel(). Use
defineOptions() for options that <script setup> doesn't natively support (name, inheritAttrs: false). Full macro
catalog in ${CLAUDE_SKILL_DIR}/references/sfc.md.
Directive shorthands — use consistently, don't mix:
:prop (v-bind:prop) — Bind attribute/prop@event (v-on:event) — Listen to event#slot (v-slot:slot) — Named slotConditional rendering:
v-if / v-else-if / v-else for conditional blocks.v-show for frequent toggles (CSS display: none, avoids mount/unmount cost).v-if for conditions that rarely change, v-show for frequent toggles.Template refs:
useTemplateRef<HTMLInputElement>('input') with matching ref attribute.ref<HTMLInputElement | null>(null) with matching ref name.<style scoped> by default. Global styles only in App.vue or layouts..parent :deep(.child-class) to style child internals (use sparingly).<style module> generates unique class names, accessed via $style.v-bind(color) in <style> uses reactive values in CSS.TodoItem not Item. Avoids HTML element conflicts.<TodoItem />. Kebab-case only in in-DOM templates.TodoItem.vue.BaseButton, BaseIcon.TodoListItem, TodoListItemButton.SearchButtonClear, SearchInputQuery.<MyComponent />.defineProps<T>() (type-based) in TypeScript projects. Object syntax in JS projects. Never array syntax in
committed code.camelCase, use in templates as kebab-case — Vue converts automatically.computed() for transformations, ref() + initial value for local copies.useComposable(() => id).defineEmits() — preferably type-based.camelCase: emit('someEvent'). Listen in kebab-case: @some-event="handler". Vue converts automatically.defineEmits<{ change: [id: number] }>().defineModel<T>() for two-way binding shorthand.defineModel<string>('firstName') with v-model:first-name="first" on parent.<slot /> in child, content between tags in parent.<slot name="header" /> in child, <template #header> in parent.<slot :item="item" />, consume with <template #default="{ item }">.Symbol keys (InjectionKey<T>) for type safety — avoid string collisions.keys.ts file.readonly() refs to prevent consumers from mutating state.undefined in the consumer.v-for always has :key — stable, unique identifiers.v-if on same element as v-for. Use computed to filter, or wrap with <template v-for>.computed or functions.use: useMouse, useFetch, useAuth.camelCase naming: useEventListener not use-event-listener.use prefix signals the function uses Vue reactivity and must be called within setup() or <script setup>.Follow this order: (1) reactive state, (2) logic that modifies state, (3) lifecycle hooks for side effects, (4) return refs.
Always return a plain object containing refs. Never return a reactive() object — it loses reactivity on destructure.
If the consumer wants an object, they can wrap: const mouse = reactive(useMouse()).
Accept refs, getters, and raw values. Use toValue() (with MaybeRefOrGetter<T> type) to normalize inputs inside
watchEffect so reactive sources are tracked.
onUnmounted() — event listeners, timers, subscriptions.onMounted() / onUnmounted(), not at top level.Composables can call other composables — build complex logic by composing simple hooks.
<script setup> or setup() function.<script setup> restores the active instance after await, so composables work after await in
<script setup>.onMounted() is acceptable.Composables are explicit, namespaced through destructuring, and integrate with Vue's reactivity and lifecycle.
defineProps<T>() with an interface.const { title, count = 0 } = defineProps<Props>().withDefaults(defineProps<Props>(), { count: 0 }). Mutable reference defaults (arrays,
objects) must use factory functions with withDefaults.Use named tuple syntax (3.3+): defineEmits<{ change: [id: number] }>().
ref(0) is Ref<number>.ref<string | number>('2024').ref<User | null>(null).ref<ResponseData>() yields Ref<ResponseData | undefined>.Types are inferred. Use explicit generic only when inference falls short: computed<string>(() => ...).
Annotate with interface, do not use reactive<T>() generic — the returned type handles ref unwrapping differently from
the generic parameter.
useTemplateRef<HTMLInputElement>('input').useTemplateRef<InstanceType<typeof MyComponent>>('comp').ref<HTMLInputElement | null>(null).Use InjectionKey<T> for type-safe keys. String keys require generic annotation: inject<string>('key').
Use generic attribute on <script setup>: <script setup lang="ts" generic="T extends string | number">.
Type DOM events explicitly: (event: Event) then cast target: event.target as HTMLInputElement.
Use Vue's typed helpers: Ref<T>, ComputedRef<T>, MaybeRefOrGetter<T>, InjectionKey<T>, PropType<T>,
ComponentPublicInstance. Full utility types table in ${CLAUDE_SKILL_DIR}/references/typescript.md.
defineAsyncComponent() for component-level splitting.lodash-es over lodash).<script setup> compiles to more minification-friendly code than Options API.active="item.id === activeId") instead of raw IDs to prevent
unnecessary child re-renders.v-once — render once, skip all future updates. For truly static content.v-memo — conditionally skip sub-tree updates. Accepts a dependency array; only re-renders when a dependency
changes. Use on v-for lists where most items don't change.vue-virtual-scroller, vueuc/VVirtualList).shallowRef() for large immutable datasets — mutations must replace the whole value.{ deep: true } on large objects — watch specific properties instead.useDebounceFn from VueUse).app.config.performance = true for Vue-specific performance markers.When writing Vue code:
<script setup> unless there is a documented reason not to.When reviewing Vue code:
This skill provides Vue-specific conventions. The coding skill governs workflow; language skills govern JS/TS choices; this skill governs component architecture, reactivity patterns, and framework API usage.