---
name: vue3-quasar
description: >
Vue.js 3 + Quasar Framework patterns with Composition API, Pinia state management.
Trigger: When developing Vue.js 3 applications with Quasar Framework, Composition API, Pinia, or building SPA/PWA/mobile apps.
license: Apache-2.0
metadata:
author: gentleman-programming
version: "2.0"
---
## When to Use
- Building Vue.js 3 applications with Quasar Framework
- Creating reusable, scalable, and decoupled components
- Implementing Composition API patterns
- Setting up Pinia for state management with persistence
- Developing SPA, PWA, or mobile applications with Quasar
## Critical Patterns
### Component Architecture
- **Composition API FIRST** — No Options API unless legacy
- **SFC (Single File Component)** — Template, script, and style in one `.vue` file
- **Composable for logic** — Extract complex logic to `useXxx()` composables in separate `.ts` files
- **Single Responsibility** — One concern per component. If a component grows, split into smaller child components, not separate file types
- **Props Interface** — Always define TypeScript interfaces for props
- **Emits Definition** — Explicit emit declarations
- **Slot Strategy** — Named slots for maximum flexibility
### Component File Organization
**Every component lives in its own directory:**
```
ComponentName/
├── ComponentName.vue # SFC: template + script setup + scoped styles
├── ComponentName.ts # Composable: useComponentName() with all logic
└── ComponentName.scss # Styles (optional, only if complex styles)
```
**The `.vue` file** contains the template inline, imports the composable, and optionally uses `
```
### Page with Composable (no props/emits)
**OnboardingPage.ts:**
```typescript
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
export function useOnboardingPage() {
const router = useRouter();
const name = ref('');
const saving = ref(false);
const isValid = computed(() => name.value.trim().length > 0);
async function handleSubmit() {
if (!isValid.value) return;
saving.value = true;
try {
// save logic
await router.push('/');
} finally {
saving.value = false;
}
}
return { name, saving, isValid, handleSubmit };
}
```
**OnboardingPage.vue:**
```vue
```
### Use Case Pattern
```typescript
// use-cases/createProfile.ts
import type { UserProfileRepository } from '../repositories/profile.repository.port';
import type { UserProfile } from '../types/profile.types';
export function createProfile(repository: UserProfileRepository) {
return async (data: Omit): Promise => {
const existing = await repository.get();
if (existing) {
throw new Error('Profile already exists. Use updateProfile instead.');
}
return repository.save(data);
};
}
```
**Key points:**
- One file, one operation
- Receives repository port (interface) as argument — never the concrete adapter
- Returns an async function — the actual operation
- Contains business validations and rules
- Pure function, no framework dependencies
### Pinia Store Pattern (with Use Cases)
```typescript
import { ProfileSQLiteRepository } from '../repositories/profile.sqlite-repository';
import { getProfile } from '../use-cases/getProfile';
import { createProfile } from '../use-cases/createProfile';
import { updateProfile } from '../use-cases/updateProfile';
export const useProfileStore = defineStore('profile', () => {
const repository = new ProfileSQLiteRepository();
// Wire use cases with repository
const get = getProfile(repository);
const create = createProfile(repository);
const update = updateProfile(repository);
const profile = ref(null);
const hasProfile = computed(() => profile.value !== null);
async function loadProfile() {
profile.value = await get();
}
async function saveProfile(data: Omit) {
profile.value = profile.value ? await update(data) : await create(data);
}
return { profile, hasProfile, loadProfile, saveProfile };
});
```
**Key points:**
- Store instantiates the repository and wires it into use cases
- Store actions delegate to use cases, never call repository directly
- Store manages reactive state only
### Shared Composable Pattern
```typescript
// composables/useApi.ts
export function useApi(url: MaybeRef) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
async function execute() {
loading.value = true;
error.value = null;
try {
data.value = await $fetch(unref(url));
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading.value = false;
}
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
execute,
};
}
```
## Commands
```bash
# Development
quasar dev # SPA dev server
quasar dev -m capacitor -T android # Android dev
quasar dev -m capacitor -T ios # iOS dev
# Build
quasar build # SPA production build
quasar build -m capacitor -T android # Android build
quasar build -m pwa # PWA build
# Utilities
quasar new component MyComponent # Generate component
quasar new page MyPage # Generate page
quasar new layout MyLayout # Generate layout
```
## Resources
- **Quasar Documentation**: https://quasar.dev/
- **Vue 3 Composition API**: https://vuejs.org/guide/extras/composition-api-faq.html
- **Pinia Documentation**: https://pinia.vuejs.org/