Getting started
Nuxt 4 with the community nuxt-quasar-ui module is the supported target.
Versions checked on Oct 21, 2026: nuxt 4.6.2, nuxt-quasar-ui 3.1.2, quasar 2.35.2, @quasar/extras 2.1.0, @nuxtjs/i18n 10.6.0.
Install
npx nuxi@latest module add quasar # installs and registers nuxt-quasar-ui
npm i quasar @quasar/extras
npm i -D sass # required when sassVariables points at a file
npx nuxi@latest module add i18n # @nuxtjs/i18n, see Locales
npm i -D @types/node # nuxt.config.ts imports node:url
npm i -D typescript@^6 vue-tsc # only for `nuxi typecheck`; vue-tsc 3.3 can't load TypeScript 7Tokens
Copy tokens/ from the design system repo into the project root. Don't edit the copies; update them from the repo.
my-app/
├─ app/
│ ├─ app.vue
│ ├─ layouts/default.vue # QLayout shell, see 03-patterns/page-layout.md
│ └─ pages/
├─ tokens/
│ ├─ quasar.variables.scss # SCSS tokens, compiled into Quasar
│ └─ csa.css # --csa-* runtime tokens and .csa-* classes
├─ app.config.ts # optional, runtime Quasar config
└─ nuxt.config.tsNuxt 4 puts source under app/. tokens/ stays at the root, so reference it with ~~ (root alias), not ~ (which points at app/).
Referencing tokens/ from outside the project root (monorepo)? Build both paths with fileURLToPath(new URL('../tokens/…', globalThis._importMeta_.url)) and add the folder to vite.server.fs.allow, or the dev server intermittently answers 403 for csa.css.
nuxt.config.ts
import { fileURLToPath } from 'node:url'
export default defineNuxtConfig({
compatibilityDate: '2026-10-01',
modules: ['nuxt-quasar-ui'],
app: {
head: {
link: [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap',
},
],
},
},
// The module prepends Quasar's CSS, so csa.css loads after it and wins ties.
css: ['~~/tokens/csa.css'],
quasar: {
// Absolute path: the module injects `@use '<path>' as *` into every SCSS block.
sassVariables: fileURLToPath(new URL('./tokens/quasar.variables.scss', globalThis._importMeta_.url)),
plugins: ['Notify', 'Dialog', 'Loading'],
iconSet: 'material-symbols-rounded',
autoIncludeIconSet: false, // the Google Fonts link supplies the icon font; don't bundle it
extras: {
font: null, // no Roboto; Plus Jakarta Sans comes from the Google Fonts link above
},
config: {
dark: false, // see Dark mode below
notify: { // see 02-components/notify.md
position: 'top-right',
timeout: 5000,
actions: [{ icon: 'sym_r_close', color: 'white', round: true, dense: true, 'aria-label': 'Dismiss' }],
},
},
},
})sassVariablesneeds an absolute path. With a path the module builds Quasar from source, so$primary,$h1and the other tokens reach component internals.sassmust be installed.- Brand colors come from the SCSS file. Leave
config.brandunset; it would create a second source of hex values. iconSet: 'material-symbols-rounded'makes Quasar's internal icons use the same set.app.head.linkloads Plus Jakarta Sans and the icon font from Google Fonts at runtime. No font files ship with the app.pluginslists Quasar plugins by name.NotifyandDialogback the feedback pattern.
Component defaults
nuxt-quasar-ui supports default props per component through quasar.components.defaults. This is the approved way to apply the component conventions; there are no wrapper components.
quasar: {
components: {
defaults: {
QBtn: { noCaps: true, unelevated: true },
QCard: { flat: true, bordered: true },
QInput: { outlined: true },
QSelect: { outlined: true },
QTable: { flat: true, bordered: true },
QTabs: {
noCaps: true,
align: 'left',
breakpoint: 0,
narrowIndicator: true,
activeColor: 'primary',
indicatorColor: 'primary',
},
},
},
},Dark mode
Dark mode is Quasar's Dark plugin. It toggles body--dark on <body>, which flips the --csa-* values in csa.css and Quasar's own dark styles.
<!-- app/components/DarkToggle.vue -->
<script setup lang="ts">
const $q = useQuasar()
const pref = useCookie<boolean>('csa-dark', { default: () => false })
$q.dark.set(pref.value) // runs on the server too, so SSR HTML has the right body class
function toggle() {
$q.dark.toggle()
pref.value = $q.dark.isActive
}
</script>
<template>
<!-- Toggle button: fixed name, state in aria-pressed (WCAG 4.1.2). -->
<q-btn flat round no-caps :icon="$q.dark.isActive ? 'sym_r_light_mode' : 'sym_r_dark_mode'"
aria-label="Dark mode" :aria-pressed="$q.dark.isActive ? 'true' : 'false'" @click="toggle" />
</template>Mount it once (header) or move the set call into app.vue so it runs on every request. The cookie avoids the light flash that dark: 'auto' gives dark-preferring users under SSR.
Locales
@nuxtjs/i18n serves English at / and Spanish (Mexico) under /es-mx. Messages live in i18n/locales/; Quasar's own component strings come from its lang pack and switch with the Locale.
Verification
Run from the app root, in this order. npm run a11y must report zero violations.
npx nuxi typecheck
npm run build
npx nuxi dev --port 3457 # dev server; a11y expects this port
npm run a11y # axe + structure + keyboard; needs the dev serverFull guide, with SSR notes and a Quasar CLI appendix: docs/04-setup-nuxt.md in the design system repo.