Skip to main content

Theme Scope

Theme Scope applies a different theme or color mode to part of the page, like a dark hero or a differently branded section. The rest of the page keeps its own.

Import

import { ThemeScope } from '@vortexlabs/vortex';
import type { ThemeProps } from '@vortexlabs/vortex';

Other links

Features

  • Supports fixed light and dark color modes.
  • Supports inverting the parent's color mode.
  • Supports nested scopes.
  • Supports themes and theme overrides.

Color Modes

Place ThemeScope inside VortexProvider. Set colorMode to light or dark to keep that mode when the page changes; without it, the scope follows its parent.

ThemeScope adds no layout box, so put a Stack inside for background and spacing.

Always light
Always dark
"use client";

import { Button, Stack, Text, ThemeScope } from "@vortexlabs/vortex";

export const ThemeScopeModes = () => {
  return (
    <Stack direction={{ xs: "column", sm: "row" }} gap={4}>
      <ThemeScope colorMode="light">
        <Stack backgroundColor="surface-subtlest" padding={5} gap={3} borderRadius="md">
          <Text>Always light</Text>
          <Button>Light action</Button>
        </Stack>
      </ThemeScope>
      <ThemeScope colorMode="dark">
        <Stack backgroundColor="surface-subtlest" padding={5} gap={3} borderRadius="md">
          <Text>Always dark</Text>
          <Button>Dark action</Button>
        </Stack>
      </ThemeScope>
    </Stack>
  );
};

Inverted Color Mode

Set colorMode="inverted" to use the opposite of the parent's mode, like a dark footer on a light page.

This panel uses the opposite color mode.
"use client";

import { Button, Stack, Text, ThemeScope } from "@vortexlabs/vortex";

export const ThemeScopeInverted = () => {
  return (
    <ThemeScope colorMode="inverted">
      <Stack backgroundColor="surface-subtlest" padding={5} gap={3} borderRadius="md">
        <Text>This panel uses the opposite color mode.</Text>
        <Button>Scoped action</Button>
      </Stack>
    </ThemeScope>
  );
};

Nested Scopes

Each scope inherits from its nearest parent scope, so inverting inside a dark scope gives a light region.

Dark outer panel
Light inner panel
"use client";

import { Button, Stack, Text, ThemeScope } from "@vortexlabs/vortex";

export const ThemeScopeNested = () => {
  return (
    <ThemeScope colorMode="dark">
      <Stack backgroundColor="surface-subtlest" padding={5} gap={4} borderRadius="md">
        <Text>Dark outer panel</Text>
        <ThemeScope colorMode="inverted">
          <Stack
            backgroundColor="surface-subtlest"
            padding={4}
            gap={3}
            borderRadius="sm"
          >
            <Text>Light inner panel</Text>
            <Button>Inner action</Button>
          </Stack>
        </ThemeScope>
      </Stack>
    </ThemeScope>
  );
};

Themes

Set name to a theme you've generated and imported, or an array like ["sleek", "astra"] to add an override. Without name or defaultName, the scope keeps its parent's theme.

import "./themes/sleek/globals.css";
import { Button, ThemeScope } from "@vortexlabs/vortex";

<ThemeScope name="sleek">
  <Button>Sleek action</Button>
</ThemeScope>

Use defaultName when the scope needs its own theme switcher, like a preview area. Calling setTheme from useTheme inside the scope changes only that scope; it can't change a controlled name.

import { Button, ThemeScope, useTheme } from "@vortexlabs/vortex";

const ThemeSwitch = () => {
  const { theme, setTheme } = useTheme();
  const astra = Array.isArray(theme);

  return (
    <Button onClick={() => setTheme(astra ? "sleek" : ["sleek", "astra"])}>
      {astra ? "Remove Astra" : "Apply Astra"}
    </Button>
  );
};

export const Preview = () => {
  return (
    <ThemeScope defaultName="sleek">
      <ThemeSwitch />
    </ThemeScope>
  );
};

Accessibility

Description
Changes tokens only, not semantics. Check contrast and focus rings in each theme and color mode you use.