Docs

This page isn't translated yet

Themes and backgrounds

Color-system modes, the theme and layoutBackground props, data-theme and how regions switch palettes.

On this page

A Systhema theme is a mode of the colorSystem token collection: one complete set of semantic colors. Any region of a page can switch to another theme, and any region can paint one of the theme's page backgrounds. With the default tokens the themes are default and dark, and the backgrounds are main and alternative. This page explains how the two props that control this, theme and layoutBackground, work together.

One Section, four combinations
import { Button, Heading, Paragraph, Section } from '@systhemaui/next'

const bands = [
  { theme: 'default', layoutBackground: 'main' },
  { theme: 'default', layoutBackground: 'alternative' },
  { theme: 'dark', layoutBackground: 'main' },
  { theme: 'dark', layoutBackground: 'alternative' },
] as const

export default function Demo() {
  return (
    <div className="w-full">
      {bands.map(({ theme, layoutBackground }) => (
        <Section key={theme + layoutBackground} theme={theme} layoutBackground={layoutBackground}>
          <Heading.h3>
            theme=&quot;{theme}&quot; layoutBackground=&quot;{layoutBackground}&quot;
          </Heading.h3>
          <Paragraph>The same markup resolves every color from the theme it sits in.</Paragraph>
          <Paragraph>
            <Button.a href="#" variant="primary">
              Get started
            </Button.a>
          </Paragraph>
        </Section>
      ))}
    </div>
  )
}

How a theme worksLink to this section

Each colorSystem mode becomes a block of --color-* variables. The first mode is the default and is bound to :root as well as its own selector; every other mode is bound to a data-theme attribute:

:root,
[data-theme='default'] {
  --color-foundations-bg: #ffffff;
  --color-foundations-text: #000000;
  --color-typography-heading: var(--color-foundations-text);
}

[data-theme='dark'] {
  --color-foundations-bg: #0a0a0a;
  --color-foundations-text: #ffffff;
  --color-typography-heading: var(--color-foundations-text);
}

Every mode declares the same variable names with its own values. Components never name a theme; they read the variables, so a data-theme attribute on any ancestor switches everything inside it. The nearest data-theme wins, so themed regions can nest.

The variables start from a set of foundations (bg, text, text-muted, surface-bg, primary-bg, line, …) and every component color (--color-button-primary-normal-background, --color-card-…) refers back to them. See Color tokens for the structure and Colors for every value.

The theme propLink to this section

Layout components take theme?: ColorSystem and set data-theme on their root: Article, Section, Feature, Card, the heroes, Header, Footer, Gallery and the posts components. ColorSystem is generated from your tokens, so it lists your project's themes.

A theme only changes variables. It sets no color and no background of its own, which has two consequences:

  • Systhema components set their own colors from the variables (Heading carries color-heading, Paragraph color-body), so they follow the theme. A plain <span> you write inside a themed region inherits whatever color its parent set; give it a token color such as text-(--color-foundations-text).
  • A themed region without a background keeps the page background behind it. Pair theme with layoutBackground whenever the region should look different, or light text ends up on a light page. In the preview the first band is there, but its white text is invisible on the light page:
theme without a background
import { Heading, Paragraph, Section } from '@systhemaui/next'

export default function Demo() {
  return (
    <div data-theme="default" className="bg-layout-main w-full">
      <Section theme="dark">
        <Heading.h3>theme=&quot;dark&quot; only</Heading.h3>
        <Paragraph>The dark text colors land on the light page background.</Paragraph>
      </Section>
      <Section theme="dark" layoutBackground="main">
        <Heading.h3>theme=&quot;dark&quot; layoutBackground=&quot;main&quot;</Heading.h3>
        <Paragraph>The section paints the dark theme's own background.</Paragraph>
      </Section>
    </div>
  )
}

The layoutBackground propLink to this section

layoutBackground?: LayoutBackground paints one of the theme's page backgrounds with a bg-layout-<name> class, which sets background-color: var(--color-layout-bg-<name>). With the default tokens main is the theme's foundations.bg and alternative its foundations.surface-bg. Because the class reads a variable, bg-layout-alternative on a dark section paints the dark alternative.

Article, Section, Feature and Gallery take layoutBackground. Elsewhere, use the class directly: <div className="bg-layout-alternative">. Add backgrounds in Figma, or with customTokens.colorSystem.<theme>.layout.bg; the LayoutBackground type follows.

Backgrounds and section spacingLink to this section

Inside an Article, backgrounds also decide spacing. Adjacent sections with neither a theme nor a layoutBackground read as one band, so the padding between them is removed. Adjacent sections with the same theme and the same layoutBackground also merge. Give one of two neighbours a different background when they should read as two bands. See Spacing model.

Setting the page themeLink to this section

The root layout sets the page's theme and background once:

src/app/layout.tsx
<html lang="en" data-theme="default">
  <body className="bg-layout-main">{children}</body>
</html>

To let visitors switch the whole site to dark, change the data-theme attribute on <html>; every region without its own theme follows. Regions with an explicit theme keep it. The theming and dark mode recipe shows a complete toggle.

Adding a themeLink to this section

Add a mode to the colorSystem collection in Figma and re-export; the new name appears in ColorSystem and as a [data-theme='<name>'] block after pnpm sync. A mode added only through customTokens must declare every variable: anything it leaves out is inherited, already resolved, from the nearest themed ancestor, not from default. See Custom tokens.