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.
Quasar's components take their colors from eight brand colors:
primary, secondary, accentpositive, negative, info, warningdark, the surface color of dark componentsThere 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:
$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.
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.
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.
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.
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.
Brand colors only reach the parts of Quasar that use a brand color. A real theme also needs:
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.
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:
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.
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.
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.
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.
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.