Skip to main content
Version: Next (5.0)

Design tokens and dark mode

The AngularJS client exposes its look as a small set of CSS custom properties, the design tokens, named --awe-*. Components, your own stylesheets, Tailwind (@theme) and any new client read the tokens; the themes and the dark mode only change the values behind them. The tokens are emitted from one LESS file, awe-client-angular/src/main/resources/less/awe/tokens.less, on top of the current LESS build: LESS and Bootstrap 3 are still there and nothing a product does today stops working.

How a theme becomes tokens​

theme variable --primary-color set by .theme-<name>, .theme-<name>.dark (LESS themes, database themes)
│
▼
design token --awe-color-primary declared on the body (.base-theme) and on :root, never set by a theme
│
▼
consumers LESS (@primary-color), components, Tailwind @theme, the React client
  • A theme variable is what a theme overrides. They are the variables AWE already had (--primary-color, --panel-background-color...) and the ones the Themes maintenance screen writes to the database, so every existing theme, database theme and product theme keeps working with no change.
  • A token is the public name. It is declared on body.base-theme (and on :root for what is outside the body), so it is resolved with the theme variables of the active theme and mode. Switching theme or mode changes the theme variables and the tokens follow.
  • Each token reads the LESS variable of the same meaning (@primary-color is var(--primary-color, <fallback>)), so a token always has a fallback, and a product that overrides @primary-color in its own LESS keeps working.

Read tokens, do not set them. To restyle, override the theme variable of the table below in your theme.

Theme mode: light, dark and the operating system​

The mode is a class of the body next to the theme (<body class="base-theme theme-default dark">):

Body classMeaning
darkThe user chose the dark mode (info button, saved in the user settings)
lightThe user chose the light mode
noneThe user did not choose: the operating system decides through prefers-color-scheme

Every theme declares its dark variables once. They are used for .theme-<name>.dark and, for a user that did not choose, inside @media (prefers-color-scheme: dark) for .theme-<name>:not(.light):not(.dark). The explicit choice of the user always wins over the operating system.

A theme file declares both through one mixin:

.theme-sunset {
--primary-color: #7a6f6f;
// ...the light variables
}

// Dark mode: chosen by the user (.dark) or, when not chosen, by the OS (prefers-color-scheme)
@theme-sunset-dark: {
--primary-color: #7a6f6f;
// ...the dark variables
};
.awe-theme-dark(sunset; @theme-sunset-dark);

The themes stored in the database get the same pair of rules from /css/themeVariables.css.

A product that must not follow the operating system​

Set the LESS variable after importing AWE (the user can still choose the dark mode, the OS is only ignored):

@awe-follow-os-color-scheme: false;

The themes stored in the database follow the same switch on the server: set awe.application.theme-follow-os-color-scheme=false (default true) so /css/themeVariables.css does not emit the prefers-color-scheme rule. Set both to turn the operating system off.

Token reference​

The token names are a contract: they do not change between versions of the line. New tokens can be added.

Color​

TokenMeaningTheme variable to overrideLESS variable
--awe-color-themeAccent color of the theme--theme-color@theme-color
--awe-color-textDefault text--text-color@text-color
--awe-color-primaryPrimary actions, selection, links of the theme--primary-color@primary-color
--awe-color-on-primaryText over the primary color--primary-text-color@primary-text-color
--awe-color-secondaryDefault buttons and neutral controls--secondary-color@secondary-color
--awe-color-on-secondaryText over the secondary color--secondary-text-color@secondary-text-color
--awe-color-backgroundPage background--primary-background-color@primary-background-color
--awe-color-surfaceSecondary surface (panel footers, inputs areas)--secondary-background-color@secondary-background-color
--awe-color-borderDefault border--border-color@border-color
--awe-color-shadowColor of the shadows--shadow-color
--awe-color-menuMain menu background--primary-menu-color@primary-menu-color
--awe-color-menu-accentMain menu hover and highlight--secondary-menu-color@secondary-menu-color
--awe-color-menu-textMain menu text--menu-text-color@menu-text-color
--awe-color-navbarNavbar background--primary-navbar-color@primary-navbar-color
--awe-color-navbar-accentNavbar header--secondary-navbar-color@secondary-navbar-color
--awe-color-navbar-textNavbar text--navbar-text-color@navbar-text-color
--awe-color-navbar-dropdownDropdown menus--navbar-dropdown-color@navbar-dropdown-color
--awe-color-panelPanel and dialog background--panel-background-color@panel-background-color
--awe-color-panel-headerPanel header background--panel-header-color@panel-header-color
--awe-color-panel-borderPanel border--panel-border-color@panel-border-color
--awe-color-panel-textText over the panel header--panel-text-color@panel-text-color
--awe-color-grid-headerGrid header--grid-header-color@grid-header-color
--awe-color-grid-borderGrid borders--grid-border-color@grid-border-color
--awe-color-skeletonLoading skeleton placeholders of grids, criteria, buttons and charts (derived from the grid border)
--awe-color-skeleton-highlightShimmer highlight of the loading skeletons
--awe-color-disabledDisabled control background--disabled-color@disabled-color
--awe-color-disabled-borderDisabled control border--disabled-border-color@disabled-border-color
--awe-color-disabled-textDisabled control text--disabled-text-color@disabled-text-color
--awe-color-requiredRequired field background--required-color@required-color
--awe-color-loading-barLoading bar--loading-bar-color@loading-bar-color
--awe-color-dangerError--danger-color@danger-color
--awe-color-warningWarning--warning-color@warning-color
--awe-color-successSuccess--success-color@success-color
--awe-color-infoInformation--info-color@info-color

Typography​

TokenMeaningTheme variable to overrideLESS variable
--awe-font-family-baseFont of the application@font-family-base
--awe-font-family-monospaceCode font@font-family-monospace
--awe-font-size-baseDefault font size--primary-font-size@primary-font-size
--awe-font-size-smallSmall text--secondary-font-size@secondary-font-size
--awe-line-height-baseDefault line height@line-height-base
--awe-font-weight-normalRegular weight
--awe-font-weight-boldBold weight

Spacing, radius, shadow and size​

TokenValueTheme variable to overrideLESS variable
--awe-space-2xs2px
--awe-space-xs5px
--awe-space-sm10px (panel margin small)@panel-margin-sm
--awe-space-md15px (panel margin medium)@panel-margin-md
--awe-space-lg20px (panel margin large)@panel-margin-lg
--awe-space-xl30px
--awe-radiusDefault radius--border-radius@border-radius
--awe-radius-smHalf of the default radius
--awe-radius-lgTwice the default radius
--awe-radius-fullPills and circles
--awe-shadow-smSmall elevation--shadow-color
--awe-shadow-mdMedium elevation--shadow-color
--awe-shadow-lgLarge elevation--shadow-color
--awe-size-header-heightNavbar height--header-height@main-navbar-height
--awe-size-menu-widthMain menu width@main-menu-width

Using the tokens​

In your own CSS or LESS:

.my-card {
background: var(--awe-color-panel);
color: var(--awe-color-text);
border: 1px solid var(--awe-color-border);
border-radius: var(--awe-radius);
box-shadow: var(--awe-shadow-sm);
padding: var(--awe-space-md);
}

They change with the theme and with the mode with nothing else to do. A product that has its own LESS variables keeps them: they compile as before and the tokens that read them follow.

Mapping into Tailwind​

The tokens are what Tailwind v4 adopts (@theme, issue #755). Each token maps one to one, without renaming it:

@theme inline {
--color-primary: var(--awe-color-primary);
--color-on-primary: var(--awe-color-on-primary);
--color-surface: var(--awe-color-panel);
--color-border: var(--awe-color-border);
--radius-awe: var(--awe-radius);
--shadow-awe-md: var(--awe-shadow-md);
--spacing-awe-md: var(--awe-space-md);
}

Use @theme inline so the utilities keep the var(--awe-...) reference and the dark mode applies with no extra utility classes.

The React client​

The React client does not use the LESS themes: it uses the PrimeReact themes. So its pages can read the same names, it declares the tokens that have a direct PrimeReact equivalent in awe-client-react/src/main.css:

TokenPrimeReact variable
--awe-color-primary--primary-color
--awe-color-on-primary--primary-color-text
--awe-color-text--text-color
--awe-color-background--surface-ground
--awe-color-surface, --awe-color-panel--surface-card
--awe-color-border--surface-border
--awe-color-skeleton, --awe-color-skeleton-highlight.p-skeleton background and shimmer (themes sky and sunset)
--awe-color-danger, --awe-color-warning, --awe-color-success, --awe-color-info--danger-color, --warning-color, --success-color, --info-color
--awe-font-family-base--font-family
--awe-radius--border-radius

The React client has no dark mode yet: a React page reads the same names and gets the light values.

Checking a change​

awe-client-angular/src/test/jest/styles/designTokens.test.js compiles the whole LESS build and checks that every theme keeps its variables (light and dark, against themes.baseline.json, taken before the tokens), that the dark rules of the operating system match the explicit dark rules, that every --awe-* token is emitted and written in this guide, and that a product LESS keeps compiling. The browser test ThemeAndModeIT switches the theme and the dark mode in the application.