Skip to content

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 7

Tokens

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.ts

Nuxt 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' }],
      },
    },
  },

})
  • sassVariables needs an absolute path. With a path the module builds Quasar from source, so $primary, $h1 and the other tokens reach component internals. sass must be installed.
  • Brand colors come from the SCSS file. Leave config.brand unset; it would create a second source of hex values.
  • iconSet: 'material-symbols-rounded' makes Quasar's internal icons use the same set.
  • app.head.link loads Plus Jakarta Sans and the icon font from Google Fonts at runtime. No font files ship with the app.
  • plugins lists Quasar plugins by name. Notify and Dialog back 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 server

Full guide, with SSR notes and a Quasar CLI appendix: docs/04-setup-nuxt.md in the design system repo.