Theming

Dark mode, the colour palette, design tokens and the Rubik font.

Dark mode

There is no theme provider and no global switch. Every component takes a boolean dark property and paints itself with the dark palette when it is set:

<tuix-text dark>On a dark surface</tuix-text>
<tuix-paper elevation="2" dark>
    <tuix-button variant="secondary" dark>Cancel</tuix-button>
</tuix-paper>

The application owns the decision and passes it down. In the Tuix frontends that is a context that also writes data-theme on <html>, so page-level CSS can react to the same state:

const { isDarkMode } = useDarkModeContext()
 
return <TuixButton dark={isDarkMode}>Save</TuixButton>

dark is not inherited through the DOM. A component nested inside a dark tuix-paper still renders light unless it gets dark itself.

Colour palette

Two enums hold the palette, one per theme. They are the same names, so a component can pick a map by its dark flag:

import { DarkPalette, Palette } from 'tuix-webcomponents'
 
const border = isDark ? DarkPalette.NEUTRAL_200 : Palette.NEUTRAL_200
TokenLightDark
PRIMARY_50#F2F3FA#F4F5FB
PRIMARY_100#E3E5F4#E3E5F4
PRIMARY_200#C7CAE8#C7CAE8
PRIMARY_300#AAAFE0#AAAFE0
PRIMARY_400#8B90D2#8B90D2
MAIN#585A9D#6F73B8
DARK_MAIN#6F73B8#6F73B8
PRIMARY_600#4E508E#5C60A4
PRIMARY_700#404277#4A4E8E
PRIMARY_800#32345F#32355F
PRIMARY_900#22233F#181A32
PRIMARY_950#1F1E33#1F1E33
SECONDARY#BBADFF#BBADFF
WHITE#FFFFFF#FFFFFF
BLACK#121212#0F1117
NEUTRAL_200#FAFAFA#C9C8D3
NEUTRAL_600#b6b6b6#8A8894
SUCCESS#439F6E#6BC49D
SUCCESS_50#F1F9F5#F1F9F5
VALIDATION#FFB82E#F5C66A
VALIDATION_50#FFFBF4#FFFBF4
VALIDATION_200#FFEAC1#FFEAC1
ERROR#F93232#FF7A7A
ERROR_50#FFF5F5#FFF5F5
ERROR_200#FFD8D8#FFD8D8
SECTION_BG#FAFAFA#0E0E0E
DISABLED_BG#EBEAF4None
STROKE_GREY#E1E0E7#32355F
SHADOWrgba(66, 79, 101, 0.16)None

DarkPalette adds eight tokens with no light counterpart.

ELEVATION_1 … ELEVATION_4 (#141722, #181C29, #1D2131, #23273A) are available to an application stacking its own dark surfaces; tuix-paper computes its own dark backgrounds rather than reading them, so its elevation table is what a nested surface has to match.

CSS custom properties

The same palette ships as a stylesheet, so CSS that is not written in TypeScript can use it:

import 'tuix-webcomponents/dist/assets/tuix-styles.css'

It puts both palettes on :root at once, named after the enum key in lowercase:

.invoice-total {
    color: var(--tuix-light-color-main);
    border-bottom: 1px solid var(--tuix-light-color-neutral-200);
}
 
:root[data-theme='dark'] .invoice-total {
    color: var(--tuix-dark-color-main);
}

The stylesheet only declares variables and never styles an element, so the order of imports does not matter.

Fonts

The components render in Rubik, served from the Tuix CDN as one variable face per style. The design tokens stylesheet imports it, so importing that stylesheet is enough:

import 'tuix-webcomponents/dist/assets/tuix-styles.css'

An @import is fetched only once the stylesheet itself has parsed. Adding the link to the document head starts the request earlier, which is what the docs site does:

<link rel="preconnect" href="https://hccdn.tuix.ch" />
<link rel="stylesheet" href="https://hccdn.tuix.ch/rubik_d444e2375d.css" />

Rubik can also be served by the framework's own font pipeline instead of the CDN.

Elevation and surfaces

tuix-paper is the surface primitive: it carries the background colour, the shadow and the corner radius. Prefer nesting content in a tuix-paper over re-implementing a card, so light and dark stay consistent. See tuix-paper.

Breakpoints

The components switch layout at two widths, exported as ScreenSizes:

NameValueEffect
ScreenSizes.TABLET1280Tighter button and icon-button padding, smaller button icons
ScreenSizes.MOBILE640Tightest padding, tuix-table renders as cards, tuix-modal goes full width
import { ScreenSizes } from 'tuix-webcomponents'
 
const isMobile = window.matchMedia(`(max-width:${ScreenSizes.MOBILE}px)`).matches

On this page