Customize

FrontAlign's default stylesheet ships with a large token set: full color ramps (--blue-100--blue-900, and the same for purple, indigo, teal, green, yellow, orange, red, pink, and slate), semantic aliases (--primary, --danger, --success, --warning, --info, --muted, --accent), and component-level tokens (--btn-dark-bg, --modal-radius, --shadow-large, and dozens more).

Only a small handful of these are recognized theme keysbody, bodyText, primary, primaryContrast, font, fontMono, and spaceXsspace2xl — that you can set directly under theme (see Theme Tokens in Compiler Engine). Every other token in FrontAlign's default stylesheet, including the entire color ramp and every semantic and component token, is customized the same way as your own custom tokens: through theme.extend and theme.dark.extend.

extend is not limited to brand-new token names. If the key you provide matches an existing FrontAlign variable, your value overrides it. If it doesn't, a new variable is created. The mechanism is identical either way.

Overriding Existing Tokens

At Build Time

theme.extend and theme.dark.extend entries are merged into the same @layer theme block as FrontAlign's own tokens. The compiler's PostCSS merge step deduplicates by variable name, so your value takes priority over the shipped default:

export default {
  theme: {
    extend: {
      'blue-500': 'oklch(0.6 0.18 250)',
      'blue-600': 'oklch(0.52 0.16 250)'
    }
  }
}

Because --primary defaults to var(--blue-700), overriding a ramp step cascades to every token that references it. If you also set the recognized primary key directly, that writes a literal --primary value instead — severing the link to the ramp entirely. Prefer overriding the ramp when you want the whole palette to move together; prefer overriding primary directly when you only want that one token to change.

Ramp overrides are still subject to the compiler's dependency-aware purge (see CSS Variable Optimization in Compiler Engine). If a ramp step you override is never referenced — directly or through a dependency chain — by any scanned class, bundled component, or utility CSS, the compiler treats it as unused and drops it from the final output. Check your build output, or reference the token somewhere your JIT scan can see, if an override doesn't appear.

At Runtime

The runtime engine injects theme.extend and theme.dark.extend into a plain, unlayered <style> tag appended to document.head — separate from FrontAlign's own CSS, which ships inside @layer theme (and base, components, utilities):

<script>
  new FrontAlign({
    theme: {
      extend: {
        'blue-500': 'oklch(0.6 0.18 250)',
        'blue-600': 'oklch(0.52 0.16 250)'
      }
    }
  });
</script>

This override works regardless of where the injected <style> tag ends up relative to FrontAlign's own CSS in the DOM. Per the CSS cascade layers specification, unlayered styles always win over layered styles of equal specificity — so a plain :root { --blue-500: ...; } block outranks the same variable declared inside @layer theme, no matter which one was parsed first.

Unlike the build, the runtime never purges unused variables — every extend and dark.extend entry you provide is always injected, whether or not anything currently references it.

faConfig() (the function new FrontAlign({...}) calls internally) exits early if it detects a <link> on the page whose href matches its build-output guard — the two systems are not meant to run together on the same page. If you're mixing a compiled build with runtime calls and overrides silently stop applying, check whether that guard is firing; the exact string it matches against is worth confirming against your actual build filename.

Overriding Semantic Tokens

Semantic aliases like --danger and --accent aren't recognized keys either, so they follow the same extend path:

export default {
  theme: {
    extend: {
      danger: '#dc2626',
      accent: '#7c3aed'
    }
  }
}

Keep in mind that some semantic tokens are defined as relative color functions off another token — --accent defaults to a lightened, desaturated version of --primary via oklch(from var(--primary) ...). Overriding accent directly replaces that formula with a fixed value, so it stops following primary automatically. If you'd rather have it keep tracking primary but shift the ramp step underneath, override the ramp instead (see above).

Dark-Mode-Only Overrides

Some component tokens — --btn-dark-bg is a good example — are only declared once, in :root. FrontAlign's own [fa-theme="dark"] block never redeclares them, so dark mode currently resolves to the same value as light mode.

To give a token like this a distinct dark-mode value without touching its light-mode value, add it under theme.dark.extend — not theme.extend. theme.extend writes to :root; theme.dark.extend writes to [fa-theme="dark"].

// frontalign.config.js
export default {
  theme: {
    dark: {
      extend: {
        'btn-dark-bg': '#000000'
      }
    }
  }
}
<!-- Runtime -->
<script>
  new FrontAlign({
    theme: {
      dark: {
        extend: {
          'btn-dark-bg': '#000000'
        }
      }
    }
  });
</script>

Both compile to:

[fa-theme="dark"] {
  --btn-dark-bg: #000000;
}

Only add a token under dark.extend if you actually want it to diverge from light mode. Anything you leave out keeps resolving to its :root value under [fa-theme="dark"] too — CSS custom properties inherit unless a rule redeclares them, so there's no need to repeat unchanged tokens on the dark side.

Token Reference

Token typeExampleHow to override
Recognized theme keyprimary, body, bodyText, font, fontMono, spaceXsspace2xlSet directly under theme (or theme.dark)
Color ramp step--blue-500, --red-700, --slate-300theme.extend / theme.dark.extend
Semantic alias--danger, --success, --warning, --info, --muted, --accenttheme.extend / theme.dark.extend
Component token--btn-dark-bg, --modal-radius, --shadow-large, --input-slot-start-widththeme.extend / theme.dark.extend
Your own custom token--brand-surfacetheme.extend / theme.dark.extend

Build vs. Runtime: Override Behavior

BehaviorCompiler Engine (build)Runtime Configuration
Merge mechanismPostCSS merges into the same @layer theme block, deduplicating by variable nameInjects a separate, unlayered <style> tag that outranks any layered CSS
Unused overridesCan be purged if nothing scanned references the token or its dependency chainNever purged — every entry is always injected
Runs alongside a compiled build.cssN/A — build is the source of the final CSSNo — the runtime call exits early if it detects a matching build output <link> already on the page

Full Example

// frontalign.config.js
export default {
  theme: {
    // Shift the whole blue ramp — cascades to --primary, --link, etc.
    extend: {
      'blue-500': 'oklch(0.6 0.18 250)',
      'blue-700': 'oklch(0.5 0.15 250)',
      danger: '#dc2626'
    },

    dark: {
      // Give --btn-dark-bg its own dark-mode value
      extend: {
        'btn-dark-bg': '#000000'
      }
    }
  }
}

Everything You Can Customize

Color is the intended surface for theme.extend — this covers every color token in FrontAlign's default stylesheet: the full ramps, the semantic colors and their contrast pairs, accent, and the button color tokens. Non-color tokens (spacing, radius, shadows, breakpoints, z-index, and so on) exist in the same @layer theme block and technically follow the same mechanism, but they're not meant to be reached for here — leave them at their defaults unless you have a specific reason not to.

// frontalign.config.js
export default {
  theme: {
    extend: {
      // Color ramps — 9 palettes, steps 100–900
      // (blue, purple, indigo, teal, green, yellow, orange, red, pink, slate)
      'blue-500': 'oklch(0.6 0.18 250)',
      'blue-600': 'oklch(0.52 0.16 250)',
      'indigo-600': 'oklch(0.55 0.17 257)',
      'red-600': 'oklch(0.55 0.19 20)',
      'slate-200': 'oklch(0.9 0.01 250)',
      // ...same pattern for purple, teal, green, yellow, orange, pink

      // Semantic colors & their contrast pairs
      primary: 'var(--blue-600)',
      'primary-contrast': 'oklch(0.985 0 0)',
      danger: '#dc2626',
      'danger-contrast': 'oklch(0.985 0 0)',
      success: '#16a34a',
      'success-contrast': 'oklch(0.985 0 0)',
      warning: '#eab308',
      'warning-contrast': 'oklch(0.145 0 0)',
      info: '#0d9488',
      'info-contrast': 'oklch(0.985 0 0)',

      // Accent
      accent: '#7c3aed',
      'accent-contrast': 'var(--white)',

      // Button colors
      'btn-light-bg': 'var(--light)',
      'btn-light-border': 'var(--slate-200)',
      'btn-light-text': 'oklch(0.28 0.02 250)',
      'btn-dark-bg': 'var(--slate-900)',
      'btn-dark-border': 'var(--slate-800)',
      'btn-dark-text': 'oklch(0.98 0 0)',
      'btn-disabled': 'var(--slate-200)',
      'btn-disabled-text': 'var(--slate-400)'
    },

    dark: {
      extend: {
        // Same tokens, only where the dark-mode value should differ
        // from what it already inherits from :root
        'btn-dark-bg': '#000000',
        accent: '#a78bfa'
      }
    }
  }
}

FrontAlign