Black Friday: 50% off all products for the first 50 customers. Only 10 left. Use code
40/50 claimed
See pricing →
November 12, 2026

Quasar Color Palette Contrast Checker for WCAG

Check your Quasar color palette against WCAG: contrast for text on brand colors in light and dark mode, the stock colors that fail, and a free checker.
Quasar
Accessibility
Color
WCAG

Your Quasar color palette is eight brand colors: primary, secondary, accent, dark, positive, negative, info and warning. Every q-btn, q-badge, q-chip and banner draws from them, and Quasar doesn't check whether text on them is readable. Its accessibility docs say so directly: there's no contrast enforcement, in light or dark mode. Today we're launching the free Brand Color Contrast Checker to do that check for all eight at once. Below is what it measures, why the stock Quasar colors already fail some of it, and how to fix a failing color without losing your brand.

What WCAG asks of a Quasar color palette

WCAG 2.2 has three contrast rules that matter for brand colors:

RuleLevelMinimum ratio
1.4.3 Contrast (Minimum): normal textAA4.5:1
1.4.3: large text (at least 18pt, or 14pt bold)AA3:1
1.4.11 Non-text Contrast: UI components and statesAA3:1
1.4.6 Contrast (Enhanced): normal / large textAAA7:1 / 4.5:1

18pt is 24px and 14pt is about 18.7px, so most button labels count as normal text and need 4.5:1. Text in a disabled control is exempt.

The ratio is (L1 + 0.05) / (L2 + 0.05), where L1 is the relative luminance of the lighter color and L2 of the darker one. Relative luminance weights the linearized red, green and blue channels by how bright they look: green counts ten times as much as blue. That's why a pale yellow and a deep blue at the "same" HSL lightness give completely different results. The post on OKLCH color scales goes into why lightness is so hard to judge by eye.

The stock Quasar colors, measured

Here are Quasar 2.35.2's default brand colors (from quasar/src/css/variables.sass), measured two ways: white text on the color, which is what <q-btn color="..."> renders, and the color used as text on the default dark page ($dark-page, #121212):

ColorHexWhite text on itAs text on #121212
primary#1976D24.604.07
secondary#26A69A2.996.24
accent#9C27B06.302.97
positive#21BA452.567.29
negative#C100156.402.92
info#31CCEC1.919.79
warning#F2C0371.6911.03

Read it against 4.5:1:

  • White text passes on only three of seven: primary (barely), accent and negative. A <q-btn color="positive" label="Save" /> is 2.56:1. color="secondary" misses even the 3:1 large-text bar, by a hair.
  • Dark mode flips the problem. The colors that hold white text well are the deep ones, and deep colors used as text or icons on a dark page fail: accent, negative and even primary.
  • Quasar knows about warning. The Notify plugin's warning type sets textColor: 'dark'. A plain q-btn color="warning" doesn't: it renders white text, at 1.69:1.

None of this is a bug in Quasar. A palette can't be right for white text and for dark backgrounds at once. That's the point: you have to choose per use, per mode.

Light mode passes, dark mode doesn't

A color that works in one theme often fails in the other, and most teams only test the theme they design in. The default primary is the cleanest example: white text on #1976D2 passes at 4.60, but the same blue as a link or a text-primary label on the dark page is 4.07. In dark mode you want a lighter primary, and once it's lighter, white text on it fails, so filled buttons need dark text in dark mode.

That's why the Theme Builder treats light and dark as separate stops of one scale. By default a brand color uses shade 500 in light mode and 400 in dark mode, and text on a filled brand color uses an "inverted" token (white in light mode, neutral-900 in dark mode).

It's also a reminder to check your own defaults. With the Theme Builder's Default preset, the green primary (Tailwind green-500, #00C950) carries white text at 2.21:1 in light mode. In dark mode, green-400 with neutral-900 text is 10.02:1. If you keep that green and need AA in light mode, move the light-mode primary to shade 700 (4.94:1), as shown below.

What the Brand Color Contrast Checker does

The Brand Color Contrast Checker is built for exactly this table, for your colors instead of the defaults:

  • Enter the eight Quasar brand colors and $dark-page, or paste a Theme Builder share link or your quasar.variables.scss to load them.
  • See the WCAG 2.2 ratio for each pair that matters: white text on the color (the same pair as the color on a white page), dark text on it (text-color="dark"), and the color as text on the dark page. Pick AA, AA large or AAA.
  • Failures are flagged, with a count of the checks that fail, so you see at a glance which colors need work, and in which mode.
  • It suggests the nearest passing shade of the same hue: one click swaps it in for light mode, or adds it as a dark-mode color only.
  • It writes the code: your quasar.variables.scss values, plus a body.body--dark block for the dark-mode colors.

It runs in your browser, without signing in. Ratios are cut, not rounded, to two decimals, so a 4.499 never shows as 4.50. The table above uses the same convention.

Check contrast in code

If you want the same check in a unit test (handy when a designer changes quasar.variables.scss), the math is short:

function channel(hex: string, start: number) {
  const value = Number.parseInt(hex.slice(start, start + 2), 16) / 255
  return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
}

export function relativeLuminance(hex: string) {
  return 0.2126 * channel(hex, 1) + 0.7152 * channel(hex, 3) + 0.0722 * channel(hex, 5)
}

export function contrastRatio(a: string, b: string) {
  const [light, dark] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x)
  return (light! + 0.05) / (dark! + 0.05)
}

const brand = {
  primary: '#1976D2',
  secondary: '#26A69A',
  accent: '#9C27B0',
  positive: '#21BA45',
  negative: '#C10015',
  info: '#31CCEC',
  warning: '#F2C037',
}

for (const [name, color] of Object.entries(brand)) {
  const whiteText = contrastRatio(color, '#FFFFFF')
  const onDarkPage = contrastRatio(color, '#121212')
  console.log(name.padEnd(10), whiteText.toFixed(2), onDarkPage.toFixed(2))
}

It takes six-digit hex colors, which is what a quasar.variables.scss file holds. Quasar also ships colors.luminosity() (import { colors } from quasar), which computes the same relative luminance from a hex or rgb() string. It doesn't parse oklch(), so for an OKLCH theme, run the check on the hex values or use the tool.

How to fix a color that fails

You rarely need a new brand color. One of these usually does it:

  1. Darken the fill, keep the hue. For white text, move the token to a darker shade of the same scale: 600 or 700 instead of 500. In the Theme Builder, each color section has an "Adjust shades" menu where you pick the light-mode and dark-mode shade for the token separately.
  2. Use dark text on light brand colors. Warning, info and many greens are light colors. Give them dark text: <q-btn color="warning" text-color="dark" />, or text-color="black".
  3. Use a different shade in dark mode. Override the variables under body.body--dark, for example a lighter primary for text and links. Then check filled buttons again with dark-mode text.
  4. Separate "brand" from "text" uses. It's fine to keep a bright brand green for large illustrations and borders, and use a darker shade of it for small text. Non-text elements only need 3:1.

Whatever you change, check both modes again. A fix for one theme is the most common way to break the other.

Common questions about Quasar color palette contrast

Does Quasar check contrast for me? No. The accessibility docs list color contrast as your app's responsibility, in light and dark mode.

Do icons need 4.5:1? No. Icons that carry meaning, focus rings, input borders and similar UI parts need 3:1 against what's next to them (WCAG 1.4.11). Decorative icons have no requirement.

Is AA enough? AA (4.5:1 for normal text) is the level most audits and accessibility policies target. AAA (7:1) is stricter and hard to meet with saturated brand fills.

What about 4.49:1? It fails. WCAG gives a minimum and no rounding rule, so treat anything under 4.5 as a fail, which is why the table above shows secondary as 2.99 rather than 3.00.

Check your palette

Paste your eight colors into the Brand Color Contrast Checker, or a Theme Builder share link, and see which pairs fail in light and dark mode before your users do.