Skip to content

Theming an app

@flyze/lib-core-angular 1.0.0-alpha.34· latest

How to register a theme, activate it, consume it in stylesheets, and control light/dark.

FlyzeThemingService is providedIn: 'root' and needs no setup: the built-in flyze theme is applied on first injection, following the OS color scheme. Everything below is for customizing that. Mechanics: features/theming. Every step on this page is running on the /theming route of the playground app, including the mistakes it warns about.

The service writes CSS custom properties onto :root, so components just use them:

.card {
background: var(--system-secondary-system-background);
color: var(--system-label);
border: 1px solid var(--system-separator);
}
.card__cta {
background: var(--colors-flyze-default);
}

The variable name is the theme key with -- in front. Keys already carry their group prefix, so the group (system / colors / platform) is not repeated in the variable name. The full list of names is flyze-default-theme.tssystem-* for text/background/separator roles, colors-* for the palette, platform-* for shell chrome and status colors.

Because it is CSS, a scheme or theme switch repaints without Angular re-rendering anything. Do not read colors into TypeScript to bind them into styles.

constructor(private theming: FlyzeThemingService) {}
ngOnInit() {
this.theming.setThemeConfig('acme', {
light: {
colors: { 'colors-flyze-default': '#0055ff' },
platform: { 'platform-shell-header': '#ffffff' }
},
dark: {
colors: { 'colors-flyze-default': '#4488ff' },
platform: { 'platform-shell-header': '#1c1c1e' }
}
});
this.theming.theme = 'acme';
}

Only override what differs — every key you leave out keeps its flyze default, in both halves.

Register before activating. Assigning an unknown name logs a warning and is ignored, so theme = 'acme' before setThemeConfig('acme', …) silently leaves you on flyze. Do both in an APP_INITIALIZER or the root component’s ngOnInit, and note that setThemeConfig replaces a theme of the same name rather than extending it — call it once with the complete palette.

Values must be hex, rgb/rgba or hsl/hsla; the ThemeColor type rejects named colors.

this.theming.customPreferredColorScheme = 'dark'; // force dark
this.theming.customPreferredColorScheme = 'auto'; // follow the OS (default)

Read the current state from preferredColorScheme / preferredColorScheme$ — the effective value. Bind a scheme toggle to customPreferredColorScheme and render the state from preferredColorScheme$:

readonly scheme$ = this.theming.preferredColorScheme$;
readonly isAuto$ = this.theming.customPreferredColorScheme$.pipe(map((s) => s === 'auto'));

detectedPreferredColorScheme$ is the raw OS preference and only useful for showing what 'auto' would resolve to. Persisting the user’s choice is the app’s job — the service does not.

For values that are not part of a theme:

this.theming.setRootVariable('app-sidebar-width', '280px'); // --app-sidebar-width
this.theming.setMultipleRootVariables({ 'app-gap': '8px', 'app-radius': '12px' });
this.theming.removeRootVariable('app-sidebar-width');

These land in the same :root block, so a name colliding with a theme key will be overwritten on the next theme or scheme change.

const palette = this.theming.generateGradiationPalette('acme', 'light.colors.colors-flyze-default');
// { 'colors-flyze-default': '#0055ff', 'colors-flyze-default-L10': 'color-mix(…);', … 'colors-flyze-default-D90': … }
this.theming.setMultipleRootVariables(palette);

L10L90 mix toward white, D10D90 toward black, in 10% steps.

Every generated entry ends with a stray ; (see the limitation in features/theming). It survives being written into the :root block, but strip it if you use a value anywhere else:

const shade = this.theming
.getThemeValueGradiation('acme', 'light.colors.colors-flyze-default', {
gradiationType: 'darker',
gradiationPercentage: 20
})
.replace(/;$/, '');