Skip to content

NiceGUI And Quasar Color Theming

This reference describes how NiceGUI's Python color APIs map onto Quasar's browser-side color system. It distinguishes theme configuration from individual element colors, fixed palette colors from runtime brand roles, and palette values from dark-mode state.

The primary public references are NiceGUI styling and appearance, NiceGUI color theming, and the Quasar color palette.

Boundary At A Glance

NiceGUI does not define an independent component theme engine. It configures and consumes the Quasar color system while adding Python-facing scope, value classification, and CSS cascade behavior.

Surface NiceGUI owns Quasar or the browser owns
app.colors(...) application-wide Python configuration and custom-name registration initial Quasar brand configuration and the resulting --q-* values on each page
ui.colors(...) a page-level element and precedence over app.colors() runtime --q-* properties on document.body plus custom text-* and bg-* classes
component color= and text_color= arguments classification of supported values as Quasar, Tailwind, or CSS colors on color-aware wrappers rendering through a Quasar prop, a utility class, or an inline CSS declaration
.props("color=...") transport of the prop to the frontend component interpretation of the value by that Quasar component
.classes("text-primary bg-positive") attachment of class names and NiceGUI's CSS layer arrangement Quasar's semantic utility classes and their --q-* variable references
ui.dark_mode(...) Python control and binding with True, False, or automatic None state Quasar dark-mode state, body--light or body--dark, and dark-aware components

The central handoff is a CSS custom property. NiceGUI supplies a value such as #176b5b; Quasar components and helpers consume var(--q-primary).

Quasar Color Namespaces

Quasar exposes two materially different kinds of color name. Only one kind is changed by NiceGUI's theme APIs.

Runtime Brand Roles

Quasar's semantic brand roles are backed by root or body-level CSS custom properties. Components and semantic utility classes follow these values at runtime. NiceGUI exposes the eight Quasar brand roles and the separate dark-page surface through app.colors() and ui.colors().

NiceGUI argument CSS custom property NiceGUI default Intended meaning
primary --q-primary #5898d4 main action and brand emphasis
secondary --q-secondary #26a69a secondary brand emphasis
accent --q-accent #9c27b0 accent emphasis
dark --q-dark #1d1d1d dark component surface
dark_page --q-dark-page #121212 dark page background
positive --q-positive #21ba45 success state
negative --q-negative #c10015 error or destructive state
info --q-info #31ccec informational state
warning --q-warning #f2c037 warning state

For example, color="primary", .props("color=primary"), text-primary, and bg-primary all reach Quasar's semantic primary role. Changing that role changes every consumer of --q-primary; it does not rewrite fixed palette colors.

from nicegui import app, ui

app.colors(
    primary="#176b5b",
    secondary="#52645f",
    accent="#c05a32",
    positive="#2e7d32",
    negative="#b3261e",
    info="#276b8e",
    warning="#a86600",
    dark="#202523",
    dark_page="#151917",
)

ui.button("Save")
ui.label("Saved").classes("text-positive")

The current NiceGUI client implementation writes page-level values to document.body in colors.js. Quasar's semantic helpers reference those properties, as described under dynamic brand colors.

Fixed Palette Colors

Names such as red-5, teal-10, and blue-grey-2 belong to Quasar's compiled color list. Their text-* and bg-* classes contain fixed color values rather than references to the semantic brand variables.

Consequently:

  • ui.colors(primary="#0057b8") changes primary, text-primary, and bg-primary consumers.
  • It does not change blue, blue-6, text-blue-6, or bg-blue-6.
  • A fixed palette color can be assigned to a component, for example ui.button("Open", color="teal-7"), without adding it to the application theme.

The fixed palette is a Quasar facility bundled into NiceGUI. It is not generated by app.colors() or ui.colors().

Custom Semantic Names

Extra keyword arguments create application-specific names:

from nicegui import app, ui

app.colors(brand="#176b5b", review_required="#a86600")

ui.button("Continue", color="brand")
ui.label("Review required").classes("text-review-required")

NiceGUI normalizes underscores in Python keyword names to hyphens in browser color names. For each custom name, the client-side applyColors helper creates:

  • a --q-<name> property on document.body
  • a .text-<name> class that reads that property
  • a .bg-<name> class that reads that property

This automates the custom-class pattern shown in Quasar's adding your own colors reference. NiceGUI also registers the name in its Python-side Quasar color set so color-aware wrappers pass the value as a Quasar color prop. The name must therefore be declared with app.colors() or ui.colors() before a NiceGUI component first uses it; this ordering requirement is part of the NiceGUI custom colors contract.

Scope And Precedence

The effective palette has three levels:

Level Scope Effect
bundled Quasar values every page fallback values supplied by Quasar's CSS
app.colors(...) all NiceGUI pages populates NiceGUI's Quasar brand configuration before each client app starts
ui.colors(...) current page writes the core and custom properties on that page's document.body and takes precedence over app-wide values

app.colors() is configuration, not a rendered UI element. NiceGUI stores its values in the application's Quasar configuration; see the current App.colors implementation.

ui.colors() is rendered into a specific page. Its DOM placement in a row, card, or other container does not scope the palette to that subtree because its client component writes to document.body. A page with two calls therefore has one effective page palette, with the last mounted call determining the core values. Subtree-specific theming requires application CSS variables or directly scoped --q-* overrides, not nested ui.colors() elements.

The ui.colors() initializer supplies all nine core values. A call such as ui.colors(primary="#555") is therefore a complete core-palette assignment: unspecified roles resolve to NiceGUI's defaults rather than acting as a one-property patch over app.colors(). Pages that must retain customized app-wide secondary, status, or dark values should pass those values explicitly in the page override.

app.colors() was added in NiceGUI 3.6.0, while custom colors were added to ui.colors() in 2.2.0. Applications pinned to earlier NiceGUI releases need version-matched behavior from the NiceGUI colors reference.

Element Color Values

On elements implemented with NiceGUI's color mixins, a color, text_color, or corresponding setter value is classified in this order by color_elements.py:

Input kind Example NiceGUI output Theme response
Quasar semantic, fixed, or registered custom name primary, red-5, brand Quasar component color prop semantic and custom names follow --q-*; fixed names do not
recognized Tailwind color red-500 bg-red-500 or text-red-500 class independent of the Quasar palette
other CSS color value #ff0000, rgb(255 0 0), rebeccapurple inline background-color or color independent of the Quasar palette
None None removes the managed color falls back to component and cascade defaults

This classification is a NiceGUI convenience, not a general Quasar rule. Passing .props("color=#ff0000") bypasses NiceGUI's color mixin and asks the Quasar component to interpret #ff0000 as its color prop. Likewise, components that expose a raw Quasar color prop without using the mixin may accept only the values documented by that component. The specific NiceGUI constructor documentation remains authoritative for each element.

Quasar and Tailwind color classes share the same HTML class list but not the same namespace conventions. text-red-5 is a Quasar fixed-palette helper; text-red-500 is a Tailwind-compatible utility. Semantic names such as text-primary are Quasar helpers.

Palette Values And Dark Mode Are Separate

The dark and dark_page arguments define colors; they do not enable dark mode. Mode state is controlled by ui.dark_mode(), the dark argument of ui.run(), or a page decorator. ui.dark_mode() takes precedence for its page and maps None to Quasar's automatic system-preference mode.

When dark mode is active, Quasar:

  • applies body--dark instead of body--light
  • uses the dark page background and dark-aware component behavior
  • automatically enables the dark state of Quasar components that support a dark prop

These behaviors are defined by Quasar dark mode. Application-owned surfaces can key off the same body class and reuse Quasar variables:

:root {
    --app-surface: #ffffff;
    --app-text: #202623;
}

.body--dark {
    --app-surface: var(--q-dark);
    --app-text: #eef3f0;
}

Changing --q-dark while the page remains in light mode changes consumers of the dark role but does not add body--dark. Enabling dark mode without designing application-specific text, border, and surface tokens does not automatically recolor arbitrary custom CSS.

CSS Classes And Cascade

NiceGUI ships Quasar's color helpers, so .classes("text-primary") and .classes("bg-warning") can be attached directly to NiceGUI elements. Quasar defines these helpers with !important.

NiceGUI changes the cascade arrangement around the bundled Quasar CSS. Its CSS layer reference explains how Quasar rules are split into layers so important Tailwind utilities or application rules in suitable layers can override them. This is a NiceGUI integration detail; the class names and color semantics still come from Quasar.

Direct CSS can consume the same semantic properties without a Quasar class:

.app-focus-ring {
    outline: 2px solid var(--q-primary);
}

Such CSS follows runtime palette changes because it reads the same property. A literal declaration such as outline-color: #176b5b does not.

Source Index