Theming

Every component accepts a themeOptions prop of type CustomThemeOptions. The same object shape works across every component, so a single theme can style your entire integration.

You can apply themes in two ways:

  1. Per component — pass themeOptions directly. Useful when different widgets need different looks.
  2. Globally — wrap your app in ThemeProvider and omit themeOptions per component.

Top-level options

interface CustomThemeOptions {
  mode?: "light" | "dark";
  colorSchemes?: {
    light?: CustomColors;
    dark?: CustomColors;
  };
  customBreakpoints?: CustomBreakpoints;
  spacingScale?: CustomSpacingScale;
  customFontFamily?: {
    light?: FontFamilyConfig;
    dark?: FontFamilyConfig;
  };
  customRadius?: {
    light?: CustomBorderRadius;
    dark?: CustomBorderRadius;
  };
  border?: {
    light?: CustomBorderSize;
    dark?: CustomBorderSize;
  };
  imageBackgroundGradient?: ImageBackgroundGradient;
}

Mode

"light" (default) or "dark". Components automatically pull color and font tokens from the active mode.

<ClassicQuizPlay {...otherProps} themeOptions={{ mode: "dark" }} />

Color schemes

Define semantic palette tokens per mode. The shape is identical for light and dark:

const themeOptions: CustomThemeOptions = {
  mode: "light",
  colorSchemes: {
    light: {
      palette: {
        primary: {
          plainColor: "#1A77D2",
          outlinedBorder: "#1A77D2",
          onPrimary: "#FAFAFA",
          primaryContainer: "#2397F3",
        },
        success: { plainColor: "#4CAF50", outlinedBorder: "#4CAF50", softBg: "#E3FBE3" },
        danger:  { plainColor: "#F44336", outlinedBorder: "#F44336", softBg: "#FEE4E2" },
        warning: { plainColor: "#DC6803", softBg: "#FEF0C7" },
      },
      textPrimary: "#212121",
      textSecondary: "#212121",
      textBody: "#424242",
      textColor: "#212121",
      textDisabled: "#212121",
      surface: "#FFFFFF",
      onSurface: "#F5F5F5",
      surfaceVariant: "#EEEEEE",
      surfaceTintDim: "#212121",
      surfaceInverse: "#F5F5F5",
      outlineEnabledBorder: "#E0E0E0",
      secondaryContainer: "#BDBDBD",
    },
  },
};

Palette tokens

TokenUsed for
primary.plainColorPrimary buttons, accent strokes
primary.primaryContainerFilled containers and highlighted backgrounds
primary.onPrimaryText/icon color on primary.plainColor
primary.outlinedBorderOutlined-button borders
success.* / danger.* / warning.*Status colors for correct/incorrect/warning states

Surface and text tokens

TokenUsed for
surfaceMain card background
onSurfaceSecondary surface (input fields, footer)
surfaceVariantTertiary background (badges, disabled rows)
surfaceInverseInverted surface (overlays, tooltips)
surfaceTintDimSubtle tint over surfaces
outlineEnabledBorderDefault border color
textPrimary / textSecondary / textBody / textColor / textDisabledText colors at different emphases

Typography

Provide a primary and secondary font family per mode:

const themeOptions: CustomThemeOptions = {
  customFontFamily: {
    light: {
      primary: "Inter, sans-serif",
      secondary: "Roboto, sans-serif",
    },
    dark: {
      primary: "Inter, sans-serif",
      secondary: "Roboto, sans-serif",
    },
  },
};

Web fonts with FontConfig

To pull a web font automatically (e.g., from Google Fonts), use a FontConfig object instead of a string:

import { FontConfig } from "fansunited-frontend-core";

const customFont: FontConfig = {
  family: "Inter",
  url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
  weights: [400, 500, 600, 700],
  display: "swap",
};

const themeOptions: CustomThemeOptions = {
  customFontFamily: {
    light: { primary: customFont, secondary: "Roboto, sans-serif" },
    dark:  { primary: customFont, secondary: "Roboto, sans-serif" },
  },
};

The component injects the @import URL into its shadow root so the font is available regardless of host-page CSS.

Spacing scale

Override the spacing scale used for paddings, margins, and gaps:

const themeOptions: CustomThemeOptions = {
  spacingScale: {
    "3xs": "2px",
    "2xs": "4px",
    xs: "8px",
    sm: "12px",
    md: "16px",
    lg: "24px",
    xl: "32px",
    "2xl": "40px",
    "3xl": "48px",
  },
};

Corner radius

Per-mode radius tokens:

const themeOptions: CustomThemeOptions = {
  customRadius: {
    light: {
      none: "0px",
      "2xs": "2px",
      xs: "4px",
      sm: "8px",
      md: "12px",
      lg: "16px",
      xl: "24px",
      "2xl": "32px",
      full: "1000px",
    },
    dark: {
      // …same structure
    },
  },
};

Border size

const themeOptions: CustomThemeOptions = {
  border: {
    light: { size: "1px" },
    dark:  { size: "2px" },
  },
};

Breakpoints

Override the responsive breakpoints used inside the components:

const themeOptions: CustomThemeOptions = {
  customBreakpoints: {
    values: {
      xs: 0,
      sm: 444,
      md: 600,
      lg: 900,
      xl: 1200,
      xxl: 1536,
    },
  },
};

Image background gradient

Used for hero images that overlay text. Configurable per mode and per template:

const themeOptions: CustomThemeOptions = {
  imageBackgroundGradient: {
    light: {
      standard: "linear-gradient(270deg, rgba(255,255,255,0) 0%, rgba(18,18,18,0.8) 100%)",
      split:    "linear-gradient(270deg, rgba(255,255,255,0) 0%, rgba(18,18,18,0.8) 100%)",
    },
    dark: {
      standard: "linear-gradient(270deg, rgba(255,255,255,0) 0%, rgba(18,18,18,0.8) 100%)",
      split:    "linear-gradient(270deg, rgba(255,255,255,0) 0%, rgba(18,18,18,0.8) 100%)",
      overlay:  "linear-gradient(270deg, rgba(255,255,255,0) 0%, rgba(18,18,18,0.8) 100%)",
    },
  },
};

Full example

import { CustomThemeOptions } from "fansunited-frontend-core";

export const brandTheme: CustomThemeOptions = {
  mode: "dark",
  colorSchemes: {
    dark: {
      palette: {
        primary: {
          plainColor: "#FF5722",
          primaryContainer: "#FF7043",
          onPrimary: "#FFFFFF",
        },
      },
      surface: "#1A1A1A",
      surfaceVariant: "#2A2A2A",
      textPrimary: "#FAFAFA",
      textSecondary: "#E0E0E0",
      outlineEnabledBorder: "#3A3A3A",
    },
  },
  customFontFamily: {
    dark: {
      primary: "Inter, sans-serif",
      secondary: "Roboto, sans-serif",
    },
  },
};

<ClassicQuizPlay {...otherProps} themeOptions={brandTheme} />;

Tips

  • Set theme once, reuse everywhere — define a single CustomThemeOptions object and either pass it via ThemeProvider or import it where needed.
  • Only the mode you use matters — if mode is "light", the dark color scheme is ignored. You can set both to support runtime mode toggling.
  • Partial overrides work — you only need to define tokens you want to change. Everything else falls back to the default theme.