Claude-skill-registry conventions-vue
Apply when working with Vue components, composables, stores, or styling. Ensures code matches established project patterns.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/conventions-vue" ~/.claude/skills/majiayu000-claude-skill-registry-conventions-vue && rm -rf "$T"
manifest:
skills/data/conventions-vue/SKILL.mdsource content
Vue Conventions
Component Structure
Always:
<script setup lang="ts"> — no Options API, no class components, no mixins.
Props
// Library components (ui-vue): export interface for reuse export interface BaseInputProps { modelValue: string | number; type?: "text" | "email" | "number"; disabled?: boolean; } const props = withDefaults(defineProps<BaseInputProps>(), { type: "text", }); // App components: local interface interface Props { status: string; variant?: "solid" | "outline"; } const props = withDefaults(defineProps<Props>(), { variant: "solid", });
Emits
// Typed tuple syntax const emit = defineEmits<{ "update:modelValue": [value: string]; focus: []; }>(); // v-model via defineModel const open = defineModel<boolean>("open", { required: true });
File Organization
Components high-level directory (atomic design)
→atoms/
prefix (BaseButton, BaseInput, BaseCard)Base*
→ Descriptive (FormControl, TabsGroup)molecules/
→ Complex (DataTable)organisms/
→ FlexLayout, ModalContainer, SlideOutlayouts/
Domain high-level directory
→ Shareddomain/common/
→ Feature-specific, ie: domain/radar/domain/{feature}/
Other high-level directories
→ Feature hookscomposables/
→ Piniastores/
→ API layerservices/
→ TypeScript definitionstypes/
Composables
Naming:
use* prefix — useModal, useAirportAutocomplete
Return pattern: Object with refs, computed, methods
export const useAirportAutocomplete = () => { const results = ref<Airport[]>([]) const loading = ref(false) const error = ref<string | null>(null) const search = async (term: string) => { ... } return { results, loading, error, search } }
Options for complex composables:
interface UseModalSubmissionOptions<T, R> { defaultFormData: T; submitAction: (data: T) => Promise<R>; onSuccess?: (result: R) => void; }
Pinia Stores
Options API style (not setup stores):
export const useSessionStore = defineStore('session', { state: (): SessionState => ({ user: null, isLoading: false, error: null }), actions: { async checkSession() { ... }, } })
- Explicit state interfaces
- All API calls in actions
- URL as source of truth for query state
Styling
See conventions-css skill. Key points:
for domain components, unscoped for atomic design components<style scoped>- Design tokens via CSS custom properties exclusively
- No Tailwind, utility classes, or CSS-in-JS
Common Patterns
Loading/error states:
<FlexLayout v-if="isLoading" align="center"> <BaseIcon id="spinner" inline /> Loading... </FlexLayout> <AlertMessage v-else-if="error" :message="error" type="error" /> <template v-else> <!-- Content --> </template>
Forms: FormControl molecule wrapping atoms
<FormControl v-model="formData.reason" :error="error" label="Reason" type="textarea" />
Modals: ModalContainer + useModalSubmission
<ModalContainer :is-open="open" title="Suspend" @close="open = false"> <FormControl v-model="formData.reason" /> <template #footer> <BaseButton :loading="isLoading" @click="submitForm">Submit</BaseButton> </template> </ModalContainer>
Layout: FlexLayout/FlexItem, not raw flexbox
<FlexLayout direction="column" gap="xs"> <FlexItem :span="6">Label</FlexItem> <FlexItem :span="6">Value</FlexItem> </FlexLayout>
TypeScript
- Strict mode
for object shapes (props, state)type
for unions and aliasestype
Never Do
- Options API
- Mixins
- Global components (always explicit imports)
- Vuex
- Inline styles (except dynamic values)
- Magic strings for events
- CSS-in-JS or Tailwind