Dark mode

Match your app to the phone's light or dark appearance. LynxTheme gives you the current theme as a signal, and CSS variables restyle the app from it.

Where the theme comes from

LynxTheme doesn't ask the phone directly. It reads a theme value from the host app, the native app that runs your bundle. The host app passes it in its global props, values it shares with every bundle it runs (see GlobalData). The value is 'Dark' or 'Light', and it's 'Light' when the host app passes none.

Where the app runsWhat theme() returns
Lynx ExplorerThe phone's appearance setting
iOS app from ng build --platform iosThe phone's appearance setting when the app starts
Android app from ng build --platform androidAlways 'Light': this app doesn't pass a theme yet
Web preview'Light'
Your own host appWhatever it passes as theme

Switch colors with CSS variables

Define your colors twice: light values on page, and dark values under a .dark class.

src/styles.css
page {
  --surface: #ffffff;
  --text: #1a1a1a;
  --border: #e0e0e0;
}

.dark {
  --surface: #1a1a1a;
  --text: #f0f0f0;
  --border: #333333;
}

Then bind the dark class on your root view:

src/app/app.ts
import { Component, inject } from '@angular/core';
import { LYNX_ELEMENTS, LynxTheme } from '@blotch/angular-lynx';

@Component({
  selector: 'app-root',
  imports: [LYNX_ELEMENTS],
  template: `
    <view class="app-shell" [class.dark]="theme.isDarkMode()">
      <view class="app-card">
        <text class="app-card-text">Hello in either theme</text>
      </view>
    </view>
  `,
  styles: `
    .app-shell {
      height: 100%;
      padding: 16px;
      background-color: var(--surface);
    }
    .app-card {
      border-width: 1px;
      border-style: solid;
      border-color: var(--border);
      border-radius: 8px;
      padding: 16px;
    }
    .app-card-text {
      color: var(--text);
    }
  `,
})
export class App {
  protected readonly theme = inject(LynxTheme);
}
  • CSS variables inherit, unlike other properties on Lynx. Everything inside the root view reads the .dark values once the class is on, and the light ones from page otherwise.
  • Light values sit on page, so they apply before the class is ever set.
  • Put the class on one element near the root, and keep the theme in variables. Avoid .dark .card descendant selectors: by default, Lynx doesn't re-match them when a parent's class changes, so the screen wouldn't update. A rule on the element whose class changes, like .dark, does update.

With the UI components

The UI components theme keeps its dark values in dark.css, under page.dark. Change that selector to .dark and bind the class as above. Classes like bg-background and text-foreground then follow the theme. See Theming.

Read the theme in TypeScript

src/app/status-bar.ts
import { Component, computed, inject } from '@angular/core';
import { LYNX_ELEMENTS, LynxTheme } from '@blotch/angular-lynx';

@Component({
  selector: 'app-status-bar',
  imports: [LYNX_ELEMENTS],
  template: `<text>{{ label() }}</text>`,
})
export class StatusBar {
  readonly #theme = inject(LynxTheme);

  protected readonly label = computed(() =>
    this.#theme.isDarkMode() ? 'Dark appearance' : 'Light appearance',
  );
}
  • theme() is a signal of 'Dark' | 'Light'.
  • isDarkMode() is a signal that's true when the theme is 'Dark'.

Use them for things CSS can't reach, such as picking an image or a chart palette. For colors, prefer the CSS variables above. The whole page restyles from one class change.

Limitations

  • prefers-color-scheme doesn't work. AngularLynx apps don't support @media. Use LynxTheme.
  • The generated apps don't report appearance changes yet. LynxTheme updates whenever the host app sends a new theme, but the iOS app from ng build --platform ios sends one only at startup and when its layout changes. Switching the phone's appearance while the app is open may not restyle it until the app restarts. Your own native app can send the theme whenever it changes. See Pass data from your native app.
  • Android apps from ng build --platform android always report 'Light'. Lynx Explorer on Android reports the real setting.
  • A [style.--name] binding can't set a CSS variable. Set variables in a stylesheet, as above.