Theming

Learn about how styles are managed centrally

Introduction

All components are styled via a central theme. The theme contains colours, font sizes, shadow styles and many more. It's intended to be the source of truth for all styles that use Vitality. If necessary, Vitality can display different colour modes (for example dark mode), seasonal themes 🎄 or even different brands. Whilst it is not built to provide ultimate flexibility, it is possible to provide these separate colour palettes.

Currently, the design system itself dictates what themes are available, as it is not encouraged for consumers to re-theme without consulting the design system owners first.

Theme Object

The theme object is a JavaScript representation of the central theme – you can explore it below. As explained in styling components in code, when you use the styled API to create styled components, all CSS properties are mapped to values in the theme object.

Example CSS using the theme object

const MyBox = styled("div", {
  background: "$primary9",
  borderRadius: "$default",
});

<MyBox />;
// $primary9 maps to theme.colors.primary
// $default maps to theme.radii.default

Switching between themes or colour modes

By default, Vitality components are theme-aware – so there's no need for any sort of provider. The provider (as described below) basically just allows your app to access the other available themes (for instance, dark mode) and switch between them.

Provide the theme(s) to your app

Wrap your app in VitalityProvider (optional).

import { VitalityProvider } from "@vitality-ds/components";

<VitalityProvider>
  <main>App</main>
</VitalityProvider>;

Toggling the theme

It's often handy to let the user decide on their theme. Vitality provides the ability to read the current mode and a function to "cycle" through all available modes.

import { ThemeContext } from "@vitality-ds/components";

const themeContext = useContext(ThemeContext);

<MyThemeToggle onClick={themeContext.cycleTheme}>
  Mode: {themeContext.theme}
</MyThemeToggle>;

Enforcing a theme

While it is handy to let the user or their system directly control the theme, there might be times where the theme should be overridden and enforced. For example adding a Vitality-based MFE (micro front-end) into an existing system with a set theme. To override the theme to "light" or "dark" add the optional prop themeOverride to the provider's theme config.

import { VitalityProvider } from "@vitality-ds/components";

<VitalityProvider config={{ theme: { themeOverride: "light" } }}>
  <main>App</main>
</VitalityProvider>;
// or
<VitalityProvider config={{ theme: { themeOverride: "dark" } }}>
  <main>App</main>
</VitalityProvider>;

A few things happening here:

  1. useContext(ThemeContext) reads from the VitalityProvider via React's Context API and provides both the current theme and the function to cycle through available modes.
  2. cycleTheme() is a function that gets triggered by which ever component you'd like to trigger it. Often it's a button.
  3. theme is a string that presents the name of the current mode. Useful for displaying to the user.

VitalityProvider Behaviour

The theme provider prioritises themes in the following order:

  1. Has the user just changed their system's theme? If so, the provider will react immediately. This will not be saved.
  2. Has the user explicitly selected the app's theme via some sort of UI control?
  3. If not, the app will read from the system's colour preference via the CSS @media selector prefers-color-scheme
  4. Default to "light".

Nesting a theme

For some cases we might want to nest a theme inside another theme. This is useful for example if you want to have a section of the UI always in dark mode even if the rest of the UI can be toggled. You can do this by wrapping the relevant components in a DarkModeProvider or LightModeProvider. This will override the current theme for all components inside the provider. These providers do not respond to the system's theme or the user's preference.

// import { DarkModeProvider, LightModeProvider } from "@vitality-ds/components";
() => {
  return (
    <DarkModeProvider>
      <DocsBox css={{ backgroundColor: "$neutral3", padding: "$xl" }}>
        <Typography>Some text in dark mode</Typography>
        <DocsBox
          css={{
            backgroundColor: "$neutral5",
            padding: "$xl",
          }}
        >
          <Stack
            direction="horizontal"
            spacing="lg"
            align="center"
            justify="between"
          >
            <Typography>A button in dark mode</Typography>
            <Button appearance="primary" onClick={() => console.log("clicked")}>
              Button
            </Button>
          </Stack>
          <LightModeProvider>
            <DocsBox css={{ backgroundColor: "$neutral1", padding: "$xl" }}>
              <Typography>Nested light mode</Typography>
            </DocsBox>
          </LightModeProvider>
        </DocsBox>
      </DocsBox>
    </DarkModeProvider>
  );
};

Getting the current theme

You can do this two ways:

  1. In JavaScript, via useContext(ThemeContext) pattern (see above)
  2. In a style object (eg. style.css.ts), by importing the theme from @vitality-ds/system and using it as a CSS selector. See below

Example

The use case here is if you wanted to add custom styling for a component based on its theme. Under a theme's CSS selector, all values will map to that theme's colour values, rather than the default. The theme keys (eg. $primary9) should be the same as the default theme.

import { AVAILABLE_THEMES } from "@vitality-ds/system";

const { dark: darkTheme } = AVAILABLE_THEMES;

const styles = css({
  backgroundColor: "$primary9",

  // Note here, that the darkTheme returns the CSS selector
  [`.${darkTheme} &`]: {
    backgroundColor: "$cyan12",
    "&:hover": {
      backgroundColor: "$cyan10",
    },
  },
});

<MyComponent css={styles} />;

Available Themes

Adding more modes

It's currently not possible to add more themes. If you see a legitimate need/desire from customers, please reach out to the Design System Community of Practice.

Props

config

Type

{ themeOverride?: MediaTheme; }

© 2025