Toggle light and dark mode using the React useTheme hook or the Angular GoabThemeService. The user’s choice persists across sessions.
Dark mode is still being tested. The CSS API and tokens may evolve as we iterate.
Dark theme styles are included in @abgov/web-components/index.css. If you
completed the developer setup,
you do not need another package or stylesheet.
How it works
Both the React provider and the Angular service write a data-theme
attribute on the document root (<html>). Design tokens scoped
to :root[data-theme=“dark”] activate via CSS cascade,
automatically applying dark colors to every component without re-rendering.
Initial mode priority
On first load, the active mode is resolved in this order:
- The user’s previously chosen mode, read from local storage
- The operating system preference (light or dark)
- The framework default (light)
React
The React package exposes a context provider and a hook.
1. Wrap your app with GoabThemeProvider
Place the provider near the top of your app, alongside other framework
providers such as BrowserRouter:
// main.tsx
import { GoabThemeProvider } from "@abgov/react-components";
import { BrowserRouter } from "react-router-dom";
import "@abgov/web-components/index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<GoabThemeProvider>
<App />
</GoabThemeProvider>
</BrowserRouter>,
);
2. Read and toggle theme with useTheme
Inside any component below the provider, call useTheme() to read
the current mode and switch theme:
import { GoabButton, useTheme } from "@abgov/react-components";
function ThemeToggle() {
const { mode, toggle } = useTheme();
const isDark = mode === "dark";
return (
<GoabButton
type="tertiary"
leadingIcon={isDark ? "sunny" : "moon"}
onClick={toggle}
>
{isDark ? "Light mode" : "Dark mode"}
</GoabButton>
);
}
API
interface GoabThemeContextValue {
/** Current theme mode. */
mode: "light" | "dark";
/** Set theme to a specific mode. */
setMode: (next: "light" | "dark") => void;
/** Flip between light and dark. */
toggle: () => void;
}
interface GoabThemeProviderProps {
children: ReactNode;
/** Initial mode used when no stored or system preference is found. @default "light" */
defaultMode?: "light" | "dark";
}
useTheme throws if called outside of GoabThemeProvider.
Angular
The Angular package exposes a root-provided service backed by a Signal.
1. Add the component stylesheet
The component stylesheet includes the dark theme styles. If you completed the developer setup, you already have this import in your global stylesheet:
@import “@abgov/web-components/index.css”;
2. Inject GoabThemeService
The service is provided in root, so inject it from any component or service:
// app.component.ts
import { Component, inject } from "@angular/core";
import { GoabThemeService } from "@abgov/angular-components";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
})
export class AppComponent {
readonly theme = inject(GoabThemeService);
toggleTheme() {
this.theme.toggle();
}
}
3. Bind to the signal in templates
Read theme.mode() as a signal — Angular tracks the dependency
automatically and re-renders when the mode changes:
<!-- app.component.html -->
<goab-button
type="tertiary"
[leadingIcon]="theme.mode() === 'dark' ? 'sunny' : 'moon'"
(onClick)="toggleTheme()"
>
{{ theme.mode() === "dark" ? "Light mode" : "Dark mode" }}
</goab-button>
API
@Injectable({ providedIn: "root" })
class GoabThemeService {
/** Read-only signal — use in templates: theme.mode() === "dark". */
readonly mode: Signal<"light" | "dark">;
/** Set theme to a specific mode. */
setMode(next: "light" | "dark"): void;
/** Flip between light and dark. */
toggle(): void;
}
Custom styling for dark mode
If you’re using design system components, they handle dark mode automatically.
If your custom CSS uses design system tokens, dark mode is handled too. The tokens cascade to their dark values when data-theme=“dark” is active.
.my-card {
background-color: var(--goa-color-surface-card);
}
Always verify in both modes.
If your custom CSS uses values that aren’t from our design tokens (specific colors, backgrounds, hardcoded styles), those won’t switch on their own. You can either swap them out for design tokens where possible, or extend dark mode by setting up your own custom property and overriding it under :root[data-theme=“dark”]:
/* Default (light) value */
:root {
--my-card-background: #f9f9f9;
}
/* Dark mode value */
:root[data-theme="dark"] {
--my-card-background: #1a1a1a;
}
/* Use it in your component */
.my-card {
background-color: var(--my-card-background);
}