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 keys — body, bodyText, primary, primaryContrast, font, fontMono, and spaceXs–space2xl — 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 functionnew FrontAlign({...})calls internally) exits early if it detects a<link>on the page whosehrefmatches 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.extendif you actually want it to diverge from light mode. Anything you leave out keeps resolving to its:rootvalue 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 type | Example | How to override |
|---|---|---|
| Recognized theme key | primary, body, bodyText, font, fontMono, spaceXs–space2xl | Set directly under theme (or theme.dark) |
| Color ramp step | --blue-500, --red-700, --slate-300 | theme.extend / theme.dark.extend |
| Semantic alias | --danger, --success, --warning, --info, --muted, --accent | theme.extend / theme.dark.extend |
| Component token | --btn-dark-bg, --modal-radius, --shadow-large, --input-slot-start-width | theme.extend / theme.dark.extend |
| Your own custom token | --brand-surface | theme.extend / theme.dark.extend |
Build vs. Runtime: Override Behavior
| Behavior | Compiler Engine (build) | Runtime Configuration |
|---|---|---|
| Merge mechanism | PostCSS merges into the same @layer theme block, deduplicating by variable name | Injects a separate, unlayered <style> tag that outranks any layered CSS |
| Unused overrides | Can be purged if nothing scanned references the token or its dependency chain | Never purged — every entry is always injected |
| Runs alongside a compiled build.css | N/A — build is the source of the final CSS | No — 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'
}
}
}
}