Skip to content

Colors

Grundtone uses 38 semantic color tokens defined in @grundtone/core. Every token maps to a CSS custom property (--color-{kebab-case}). Core is the single source of truth — design-tokens and Vue/Nuxt derive their values from it.

Naming Convention

Tokens use shade-based naming (primaryLight, primaryDark) instead of state-based (primaryHover, primaryActive). Components decide which shade to use for hover, active, tint, etc. This is more flexible and consistent across the system.


Brand

ColorTokenCSS VariablePurpose
#0059b3primary--color-primaryPrimary brand color
#3381ccprimaryLight--color-primary-lightLighter shade (hover, tints)
#003a7aprimaryDark--color-primary-darkDarker shade (active, pressed)
#ffffffonPrimary--color-on-primaryText on primary background
#6c757dsecondary--color-secondarySecondary brand color
#868e96secondaryLight--color-secondary-lightLighter secondary shade
#494f54secondaryDark--color-secondary-darkDarker secondary shade

Status

ColorTokenCSS VariablePurpose
#198754success--color-successSuccess state
#d1e7ddsuccessLight--color-success-lightSuccess background tint
#146c43successDark--color-success-darkDarker success shade
#ffc107warning--color-warningWarning state
#fff3cdwarningLight--color-warning-lightWarning background tint
#cc9a06warningDark--color-warning-darkDarker warning shade
#dc3545error--color-errorError state
#f8d7daerrorLight--color-error-lightError background tint
#b02a37errorDark--color-error-darkDarker error shade
#0dcaf0info--color-infoInfo state
#cff4fcinfoLight--color-info-lightInfo background tint
#0aa2c0infoDark--color-info-darkDarker info shade

Surface

ColorTokenCSS VariablePurpose
#ffffffbackground--color-backgroundPage background
#fafafabackgroundAlt--color-background-altAlternate sections, zebra rows
#f8f9fasurface--color-surfaceCards, panels
#f0f1f2surfaceAlt--color-surface-altHover state, zebra
#ffffffsurfaceRaised--color-surface-raisedModals, FABs
rgba(255,255,255,0.95)surfaceOverlay--color-surface-overlaySemi-transparent overlay
rgba(0,0,0,0.5)modalBackdrop--color-modal-backdropModal backdrop scrim

Background Utilities

Shorthand classes for applying surface/background tokens:

ClassCSS Variable
.bg-normalvar(--color-background)
.bg-altvar(--color-background-alt)
.bg-surfacevar(--color-surface)
.bg-surface-altvar(--color-surface-alt)
.bg-surface-raisedvar(--color-surface-raised)
.bg-overlayvar(--color-surface-overlay)
.bg-modalvar(--color-modal-backdrop)
html
<!-- Modal backdrop -->
<div class="fixed inset-0 z-modal-backdrop bg-modal">
  <div class="z-modal">...</div>
</div>

Text

ColorTokenCSS VariablePurpose
#212529text--color-textPrimary text
#6c757dtextSecondary--color-text-secondarySecondary text
#adb5bdtextTertiary--color-text-tertiaryTertiary / hint text
#fffffftextInverse--color-text-inverseText on dark backgrounds
#a3a3a3textPlaceholder--color-text-placeholderInput placeholders
#d4d4d4textDisabled--color-text-disabledDisabled elements

Border

ColorTokenCSS VariablePurpose
#dee2e6borderLight--color-border-lightSubtle dividers
#ced4daborderMedium--color-border-mediumDefault inputs
#adb5bdborderStrong--color-border-strongFocus, emphasized
rgba(255,255,255,0.2)borderInverse--color-border-inverseOn dark backgrounds

Focus & Neutral

ColorTokenCSS VariablePurpose
#0059b3focus--color-focusFocus indicator
rgba(0,89,179,0.25)focusRing--color-focus-ringFocus ring shadow
#6c757dneutral--color-neutralNeutral UI elements

Light vs Dark Comparison

Side-by-side comparison of all tokens across modes.

Brand

ColorTokenLightDark
#0059b3primary#0059b3#4dabf7
#3381ccprimaryLight#3381cc#74c0fc
#003a7aprimaryDark#003a7a#339af0
#ffffffonPrimary#ffffff#121212
#6c757dsecondary#6c757d#adb5bd
#868e96secondaryLight#868e96#ced4da
#494f54secondaryDark#494f54#868e96

Status

ColorTokenLightDark
#198754success#198754#51cf66
#d1e7ddsuccessLight#d1e7dd#1a3d20
#146c43successDark#146c43#40c057
#ffc107warning#ffc107#ffd43b
#fff3cdwarningLight#fff3cd#3d3a1a
#cc9a06warningDark#cc9a06#fab005
#dc3545error#dc3545#ff6b6b
#f8d7daerrorLight#f8d7da#3d1a1c
#b02a37errorDark#b02a37#fa5252
#0dcaf0info#0dcaf0#4dabf7
#cff4fcinfoLight#cff4fc#1a2e3d
#0aa2c0infoDark#0aa2c0#339af0

Surface

ColorTokenLightDark
#ffffffbackground#ffffff#121212
#fafafabackgroundAlt#fafafa#1a1a1a
#f8f9fasurface#f8f9fa#1e1e1e
#f0f1f2surfaceAlt#f0f1f2#252525
#ffffffsurfaceRaised#ffffff#2a2a2a
rgba(255,255,255,0.95)surfaceOverlayrgba(255,255,255,0.95)rgba(30,30,30,0.95)
rgba(0,0,0,0.5)modalBackdroprgba(0,0,0,0.5)rgba(0,0,0,0.7)

Text

ColorTokenLightDark
#212529text#212529#ffffff
#6c757dtextSecondary#6c757d#b0b0b0
#adb5bdtextTertiary#adb5bd#808080
#fffffftextInverse#ffffff#121212
#a3a3a3textPlaceholder#a3a3a3#666666
#d4d4d4textDisabled#d4d4d4#4a4a4a

Border

ColorTokenLightDark
#dee2e6borderLight#dee2e6#404040
#ced4daborderMedium#ced4da#505050
#adb5bdborderStrong#adb5bd#606060
rgba(255,255,255,0.2)borderInversergba(255,255,255,0.2)rgba(0,0,0,0.3)

Usage

CSS

css
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-light);
  color: var(--color-text);
}

.card:hover {
  background: var(--color-surface-alt);
  border-color: var(--color-border-strong);
}

SCSS — Override Colors

Override any semantic token at build time via @use ... with ():

scss
@use '@grundtone/design-tokens/scss' with (
  $color-overrides-light: (
    'primary': #e91e63,
    'primary-light': #f06292,
    'primary-dark': #c2185b,
  ),
  $color-overrides-dark: (
    'primary': #f48fb1,
  )
);

Or import colors standalone:

scss
@use '@grundtone/design-tokens/scss/colors' with (
  $overrides-light: (
    'primary': #e91e63,
  ),
  $overrides-dark: (
    'primary': #f48fb1,
  )
);

SCSS — Raw Palette

For compile-time access to raw palette colors (not CSS custom properties), import the palette module. This is useful for generating static styles, calculating contrast, or building component variants.

scss
@use '@grundtone/design-tokens/scss/color-palette' as palette;

palette.color($family, $shade)

Get a specific shade from a color family.

scss
border-color: palette.color('blue', 300);   // #93c5fd
background: palette.color('gray', 50);      // #fafafa
color: palette.color('red', 700);           // #b91c1c

Available families: gray, blue, green, red, yellow, indigo. Available shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900.

palette.lighter($family, $shade, $steps)

Move up (lighter) in the shade scale by N steps. Clamps at 50.

scss
$base: palette.color('blue', 500);      // #3b82f6
$light: palette.lighter('blue', 500);   // #60a5fa (one step = 400)
$lighter: palette.lighter('blue', 500, 3); // #bfdbfe (three steps = 200)

palette.darker($family, $shade, $steps)

Move down (darker) in the shade scale by N steps. Clamps at 900.

scss
$base: palette.color('blue', 500);      // #3b82f6
$dark: palette.darker('blue', 500);     // #2563eb (one step = 600)
$darker: palette.darker('blue', 500, 3); // #1e40af (three steps = 800)

palette.alpha($family, $shade, $alpha)

Create a semi-transparent variant of a palette color.

scss
background: palette.alpha('blue', 500, 0.1);  // rgba(59, 130, 246, 0.1)
box-shadow: 0 4px 12px palette.alpha('gray', 900, 0.15);
border-color: palette.alpha('red', 500, 0.3);

When to use palette vs CSS custom properties

Use CSS custom properties (var(--color-primary)) for all runtime styles — they respect theming and dark mode. Use palette functions only when you need a static color value at compile time, such as SCSS calculations, contrast checks, or generating component variants that don't change with theme.

React Native

tsx
import { useGrundtoneTheme } from '@grundtone/react-native';

function Card() {
  const { theme } = useGrundtoneTheme();
  return (
    <View
      style={{
        backgroundColor: theme.colors.surface,
        borderColor: theme.colors.borderLight,
      }}
    >
      <Text style={{ color: theme.colors.text }}>Content</Text>
    </View>
  );
}

Migration from Old Names

If you are upgrading from a previous version, rename these tokens:

Old NameNew Name
primaryHoverprimaryLight
primaryActiveprimaryDark
secondaryHoversecondaryLight
secondaryActivesecondaryDark
successBgsuccessLight
warningBgwarningLight
errorBgerrorLight
infoBginfoLight
surfaceHoversurfaceAlt
borderborderLight
borderHoverborderStrong

CSS variable equivalents:

Old CSS VariableNew CSS Variable
--color-primary-hover--color-primary-light
--color-primary-active--color-primary-dark
--color-secondary-hover--color-secondary-light
--color-secondary-active--color-secondary-dark
--color-success-bg--color-success-light
--color-warning-bg--color-warning-light
--color-error-bg--color-error-light
--color-info-bg--color-info-light
--color-surface-hover--color-surface-alt
--color-border--color-border-light
--color-border-hover--color-border-strong

Theming

See Theme Configuration for the concept overview and createTheme() API. This section covers web-specific setup for each package.

CSS Variables (Web)

On web, the theme is applied as CSS custom properties on :root:

css
--color-primary
--color-primary-light
--color-primary-dark
--color-background
--color-text
/* etc. — 38 tokens total */

Use them in your styles:

css
.button {
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

Vue 3

Where: ThemeProvider in App.vue

How: Pass createTheme() to the theme prop. Override only the colors you need — the rest uses defaults.

vue
<template>
  <ThemeProvider :theme="myTheme" mode="auto">
    <RouterView />
  </ThemeProvider>
</template>

<script setup lang="ts">
  import { ThemeProvider } from '@grundtone/vue';
  import { createTheme } from '@grundtone/core';

  const myTheme = createTheme({
    light: {
      primary: '#0059b3',
      primaryLight: '#3381cc',
      primaryDark: '#003a7a',
      onPrimary: '#ffffff',
    },
    dark: {
      primary: '#4dabf7',
      primaryLight: '#74c0fc',
      primaryDark: '#339af0',
      onPrimary: '#121212',
    },
  });
</script>

Alternative: single partial theme (same overrides for both modes):

vue
<template>
  <ThemeProvider :theme="myTheme" mode="auto">
    <App />
  </ThemeProvider>
</template>

<script setup lang="ts">
  import { ThemeProvider } from '@grundtone/vue';

  const myTheme = {
    colors: {
      primary: '#e91e63',
      primaryLight: '#f06292',
      primaryDark: '#c2185b',
    },
  };
</script>

Use ThemeToggle from @grundtone/vue or useTheme().toggleMode() to let users switch themes.


Nuxt 3

Where: nuxt.config.ts

How: Add theme to the grundtone module config. Use createTheme() for separate light and dark themes.

ts
// nuxt.config.ts
import { createTheme } from '@grundtone/core';

export default defineNuxtConfig({
  modules: ['@grundtone/nuxt'],
  grundtone: {
    theme: createTheme({
      light: {
        primary: '#0059b3',
        primaryLight: '#3381cc',
        primaryDark: '#003a7a',
        onPrimary: '#ffffff',
      },
      dark: {
        primary: '#4dabf7',
        primaryLight: '#74c0fc',
        primaryDark: '#339af0',
        onPrimary: '#121212',
      },
    }),
  },
});

The Nuxt plugin applies the correct theme based on system preference (prefers-color-scheme) and reacts to changes.


Plain Web (no framework)

Where: Your own CSS or build step

How: @grundtone/design-tokens ships static :root variables. Override them in your stylesheet after importing the design-tokens CSS.

html
<link rel="stylesheet" href="node_modules/@grundtone/design-tokens/dist/index.css" />
<style>
  :root {
    --color-primary: #e91e63;
    --color-primary-light: #f06292;
    --color-primary-dark: #c2185b;
    --color-on-primary: #ffffff;
    /* Override more as needed */
  }
</style>

With a bundler (Vite, webpack), import and override in your main CSS/SCSS:

css
@import '@grundtone/design-tokens/dist/index.css';

:root {
  --color-primary: #e91e63;
  --color-primary-light: #ec407a;
}

There is no ThemeProvider for Plain Web, so no automatic light/dark switching. For dark mode, use media queries or a class:

css
@media (prefers-color-scheme: dark) {
  :root {
    --color-primary: #4dabf7;
    --color-background: #121212;
  }
}

/* Or with a class */
[data-theme='dark'] {
  --color-primary: #4dabf7;
  --color-background: #121212;
}

Custom Colors (SCSS)

Colors use the same @use ... with () override pattern as breakpoints. Default values live in _color-defaults.scss (derived from @grundtone/core). Override only what you need — the rest keeps its defaults via map.merge.

Override via the main entry point:

scss
@use '@grundtone/design-tokens/scss' with (
  $color-overrides-light: (
    'primary': #e91e63,
    'primary-light': #f06292,
    'primary-dark': #c2185b,
  ),
  $color-overrides-dark: (
    'primary': #f48fb1,
    'primary-light': #f8bbd0,
    'primary-dark': #ec407a,
  )
);

Or import colors standalone (e.g. if you only need color tokens, not the full design system):

scss
@use '@grundtone/design-tokens/scss/colors' with (
  $overrides-light: (
    'primary': #e91e63,
    'primary-light': #f06292,
    'primary-dark': #c2185b,
  ),
  $overrides-dark: (
    'primary': #f48fb1,
  )
);

Token keys use kebab-case matching the CSS variable names (e.g. 'primary-light' for --color-primary-light). See the token tables above for all available tokens.

Colors and breakpoints can be overridden together in a single @use statement.


Custom Breakpoints

Breakpoints are compiled into @media queries at build time via SCSS. The default values live in a single source of truth (_breakpoints-defaults.scss). Every file — grid utilities, container max-widths, CSS custom properties, and @property declarations — derives its values from this one map.

Override $grid-breakpoints before importing design-tokens:

scss
@use '@grundtone/design-tokens/scss' with (
  $grid-breakpoints: (
    xs: 0,
    sm: 576px,
    md: 768px,
    lg: 992px,
    xl: 1200px,
    2xl: 1400px,
  )
);

Or when importing just breakpoints:

scss
@use '@grundtone/design-tokens/scss/breakpoints' as bp with (
  $grid-breakpoints: (
    xs: 0,
    sm: 576px,
    md: 768px,
    lg: 992px,
    xl: 1200px,
    2xl: 1400px,
  )
);

Defaults: sm 640px, md 768px, lg 1024px, xl 1280px, 2xl 1536px.

The CSS custom properties --breakpoint-sm etc. are exported to :root for informational use (e.g. in JavaScript) but do not affect the compiled media queries. When you override $grid-breakpoints, the :root CSS vars update automatically.

See Breakpoints for media query mixins and utility class prefixes.