50% off for the first 50 customers. Only 10 left. Use code
40/50 claimed
See pricing →
October 30, 2026

How to Create a Custom Quasar Theme With Dark Mode

Theme Quasar by hand with Sass variables, CSS variables, setCssVar and a dark palette, or build a full theme in the free Theme Builder and export it.
Quasar
Theming
Dark Mode
Theme Builder

A custom Quasar theme starts with eight brand colors and ends wherever you stop: dark mode colors, fonts, radius, the default look of buttons and inputs. This guide shows how Quasar theming works under the hood, how to do it by hand with short code that runs on Quasar 2.35, and how to build and export a full theme with the free Quasar Theme Builder.

Two related posts cover what this one skips. Nuxt + Quasar setup walks through installing nuxt-quasar-ui and pointing it at a variables file. Design tokens in Quasar is about planning a token system for a team.

How Quasar theming works

Quasar's components take their colors from eight brand colors:

  • primary, secondary, accent
  • positive, negative, info, warning
  • dark, the surface color of dark components

There is also dark-page, the page background in dark mode.

Each brand color lives in a CSS variable on :root, named --q-<name>. Quasar's color classes read those variables:

:root {
  --q-primary: #1976D2;
  --q-dark-page: #121212;
  /* ...one per brand color */
}

.text-primary { color: var(--q-primary) !important; }
.bg-primary { background: var(--q-primary) !important; }

So color="primary" on a q-btn, the text-primary class and the bg-primary class all follow --q-primary. Change the variable and everything that uses primary changes with it.

You can set those values in three places:

  • Sass variables ($primary), at build time: a fixed brand, plus radius, fonts and other Sass-only settings.
  • framework.config.brand, at app start: brand colors without Sass.
  • setCssVar(), at any time while the app runs: colors the user picks or that come from an API.

One catch: a Sass variable used in your own styles, such as color: $primary, compiles to a fixed hex value. It will not follow setCssVar or a dark mode override. Write color: var(--q-primary) in your own CSS when the color must be able to change.

Set brand colors with Sass variables

In a Quasar CLI project, put your colors in src/css/quasar.variables.scss. Quasar compiles its own CSS from this file, and every .scss, .sass and .vue style block in the project can read the variables.

// src/css/quasar.variables.scss
$primary   : #4f46e5;
$secondary : #0ea5e9;
$accent    : #a855f7;

$dark      : #1e1b2e;
$dark-page : #12101c;

$positive  : #16a34a;
$negative  : #dc2626;
$info      : #0284c7;
$warning   : #f59e0b;

$generic-border-radius  : 8px;
$button-border-radius   : 8px;
$typography-font-family : 'Inter', sans-serif;

The last three lines show that Sass reaches further than color. $generic-border-radius and $button-border-radius set the corner radius, and $typography-font-family sets the body font. The font still has to be loaded, for example with a Google Fonts link.

If you create or delete this file while the dev server runs, restart the server. Edits to an existing file are picked up without a restart.

In Nuxt, the same file goes in assets/ and the module's sassVariables option points to it. The Nuxt + Quasar setup post shows that config.

Set brand colors without Sass

If you don't want a Sass build step, set the colors in the Quasar config. Inside the object your quasar.config file returns:

framework: {
  config: {
    brand: {
      primary: '#4f46e5',
      secondary: '#0ea5e9',
      dark: '#1e1b2e'
    }
  }
}

In Nuxt the same object goes under quasar.config.brand in nuxt.config.ts. Quasar writes these values into the --q-* variables when the app starts. Any color you leave out keeps its Sass or default value.

Change colors at runtime with setCssVar

setCssVar sets a brand color while the app runs. It takes the color name without the --q- prefix:

<script setup>
import { setCssVar } from 'quasar'

const brands = { indigo: '#4f46e5', rose: '#e11d48' }

function applyBrand (name) {
  setCssVar('primary', brands[name])
}
</script>

<template>
  <div class="q-gutter-sm">
    <q-btn color="primary" label="Indigo" @click="applyBrand('indigo')" />
    <q-btn color="primary" label="Rose" @click="applyBrand('rose')" />
  </div>
</template>

By default it writes the variable as an inline style on document.body. Pass an element as the third argument to recolor only one part of the page:

setCssVar('primary', '#e11d48', document.getElementById('promo-banner'))

Everything inside #promo-banner then uses the new primary. The rest of the page keeps the old one. getCssVar('primary') reads the current value back.

setCssVar uses document, so call it in the browser: in an event handler, in onMounted, or in a boot file that is disabled on the server for SSR builds.

Give dark mode its own colors

Turn on dark mode in the Quasar config with dark: true, false or 'auto'. 'auto' follows the operating system and updates when the OS setting changes:

framework: {
  config: {
    dark: 'auto'
  }
}

To switch it from a component, use the Dark plugin through useQuasar(). $q.dark.toggle() flips it:

import { useQuasar } from 'quasar'

const $q = useQuasar()

$q.dark.set(true) // or false, or 'auto'

When dark mode is on, Quasar adds body--dark to the body. The page background becomes --q-dark-page, the text turns white, and every component with a dark prop follows dark mode without you setting it. Dark components use --q-dark as their surface.

Quasar does not change your brand colors in dark mode. A primary that works on white is often too deep on a near-black page. Override the variables under body.body--dark, for example in src/css/app.scss:

body.body--dark {
  --q-primary: #818cf8;
  --q-secondary: #38bdf8;
  --q-dark: #1e1b2e;
  --q-dark-page: #12101c;
}

Use lighter shades of the same hues in dark mode. That keeps the brand recognizable and the contrast readable.

One sharp edge: setCssVar writes an inline style on body, and in a client-rendered app (SPA) Quasar applies config.brand the same way. An inline style beats your body.body--dark rule. So set any color that should differ between light and dark in CSS only, and keep it out of config.brand. If a color must also change at runtime, call setCssVar again whenever $q.dark.isActive changes.

Shadows have their own variables since Quasar 2.31. In dark mode Quasar draws elevation with --q-dark-shadow-color, white by default. Set it to #000 under body.body--dark if you prefer dark shadows on dark surfaces.

What hand theming leaves out

Brand colors only reach the parts of Quasar that use a brand color. A real theme also needs:

  • a neutral scale for surfaces, text and borders, in light and dark;
  • a heading font and a mono font, weights and line height;
  • one radius used by buttons, fields and cards alike;
  • default props, such as unelevated buttons or outlined inputs everywhere.

Quasar has no global setting for component prop defaults. You end up writing unelevated on every q-btn or patching components in a boot file. This is the work the Theme Builder does for you.

Build the theme in the Theme Builder

The Quasar Theme Builder is free and runs in the browser. You change the theme in a toolbar and watch it apply to real Quasar components in three previews: Components, Dashboard and Landing. You can switch the preview between light, dark and system mode at any time.

Presets. Start from one of 12 presets: Default, Mono, Cobalt, Sky, Mint, Iris, Crimson, Coral, Sunset, Carbon, Bubblegum and Parchment. A preset can set colors, fonts, radius, icons and defaults. The Default preset uses a green primary, a slate neutral, Public Sans, a 0.25rem radius and Material Symbols icons.

Colors and palettes. Each brand color and the neutral scale is a palette of 11 shades, from 50 to 950. Pick a Tailwind palette or one of the preset palettes, or open the palette editor. It edits the scale as OKLCH lightness, chroma and hue curves, and you can pin a shade to an exact HEX or OKLCH value. You also choose which shade each token uses in light and in dark mode. By default, brand colors use shade 500 in light mode and 400 in dark mode. Backgrounds, text and borders are picked from the neutral scale.

Fonts. Choose from 24 Google Fonts (sans, serif and mono), with a separate heading font and mono font. You can also set the size, line height, letter spacing and weights.

Icons. Choose one of 12 icon sets: Material Symbols (outlined or rounded), Material Icons (filled, outlined or round), Material Design Icons, Font Awesome, Bootstrap, Ionicons, Eva, Line Awesome and Themify.

Radius. Choose a radius from 0 to 0.75rem. Buttons and fields get 1.5 times that value, and cards get twice it.

Defaults. Set the global size and the default look of components:

  • buttons: solid, elevated, outline, soft, subtle, flat or push, plus color, uppercase and rounded;
  • cards: outline, elevated, flat or subtle;
  • inputs: outlined, filled, standout, standard or borderless, plus color and rounded.

Undo and redo work with Ctrl+Z and Ctrl+Shift+Z. In the Export dialog, a share link reopens the Theme Builder with your theme applied.

Export the theme into Quasar CLI

Open Export and choose Quasar CLI. You get four files:

  • src/css/quasar-theme.css: the color scales as CSS variables, the light and dark tokens, the fonts, the radius, and the styles that point Quasar's components at them;
  • quasar.config.js: the keys to merge into your config;
  • src/boot/theme-defaults.js: a boot file that sets the component defaults before the app mounts;
  • src/css/quasar.variables.scss: the light-mode colors, radius and font as Sass variables.

For the Default preset, the config keys look like this:

// quasar.config.js: merge these keys into your existing config
export default defineConfig(() => ({
  boot: ['theme-defaults'],
  css: ['quasar-theme.css'],
  extras: ['material-symbols-outlined'],
  framework: {
    iconSet: 'material-symbols-outlined',
    config: {
      dark: 'auto'
    }
  }
}))

Copy the three files into src/, merge the keys, and restart the dev server.

The CSS file defines the dark palette under body.body--dark, as in the hand-written example above. Keep dark: 'auto', or your own toggle through $q.dark, so Quasar adds the body--dark class. The Sass file holds the light-mode brand colors plus $dark and $dark-page, because Sass can't switch at runtime.

Export the theme into Nuxt

Choose Nuxt in the same dialog. You get three files:

  • assets/css/quasar-theme.css: the same stylesheet;
  • assets/quasar.variables.scss: the same Sass variables;
  • nuxt.config.ts: the quasar keys to merge into your config.

The Nuxt config also carries the component defaults, through the module's components.defaults option, so there is no boot file. Trimmed, for the Default preset:

// nuxt.config.ts: merge these keys into your existing config
export default defineNuxtConfig({
  modules: ['nuxt-quasar-ui'],
  css: ['~/assets/css/quasar-theme.css'],
  quasar: {
    iconSet: 'material-symbols-outlined',
    sassVariables: '~/assets/quasar.variables.scss',
    extras: {
      font: null,
      fontIcons: ['material-symbols-outlined']
    },
    config: {
      dark: 'auto'
    },
    components: {
      defaults: {
        QBtn: { unelevated: true, color: 'primary' },
        QInput: { outlined: true, color: 'primary' },
        QCard: { flat: true, bordered: true }
        // ...QSelect, QFile, QTable and QDate
      }
    }
  }
})

sassVariables needs the sass package installed. extras.font is null because the theme's CSS file imports its own fonts from Google Fonts.

See it on real sections: Themeable Components

Every QuasarUI Component uses Quasar's brand color names, so any of the methods above recolors them. Ten Paid Components go further. They are Themeable Components: they read the Theme Builder's surface, text, border, radius and font tokens, so the whole Component follows the theme, in light and dark.

Each preview has a picker for the 12 presets and a light/dark switch, so you can see a theme on a full section before you export it.

Copied on its own, a Themeable Component's source will not look themed. It expects the Theme Builder's quasar-theme.css in your project, because that file defines the tokens it reads. Export the theme first, then paste the Component.

Start with the Theme Builder

Pick a preset, adjust it until it looks like your product, and export it into your Quasar CLI or Nuxt project. Open the Quasar Theme Builder. It's free.