Architecture overview
style.scss determines which layer can override which.
Folder structure
All SCSS source files live undersrc/assets/scss/:
Default colour palette
Default colours are defined in_defaults.scss using the !default flag, which means they can be overridden by any value set before the import:
_variables.scss then derives contrast colours, subtle variants, and border colours from these base values and exports them as a $theme-colors map. Bootstrap uses this map to generate CSS custom properties like --bs-primary, --bs-primary-bg-subtle, etc. These defaults are what the bundle is compiled with; each location’s own values replace them in the browser (see Runtime branding), so src/theme/tokens.ts keeps a copy of them and a test fails if the two drift.
How components are styled
Components use one of three patterns (often combined):CSS Modules (scoped styles)
Component-specific styles live in a.module.scss file next to the component. Class names are locally scoped at build time so they never leak.
Bootstrap utility classes
Most layout and spacing is handled with Bootstrap 5 utility classes directly in JSX:custom/_utilities.scss with additional helpers such as fixed-pixel heights (h-40px), viewport heights (vh-100), and more.
Global SCSS (unscoped)
Some components import a plain.scss file for global styles. These affect the entire page and are typically used for animation keyframes or third-party library overrides:
Runtime branding (per-location colours)
Each location sets its brand colours in the Nexudus dashboard (Theme.PrimaryColor, Theme.SecondaryColor, Theme.Success, Theme.Info, Theme.Warning, Theme.Alert, and their dark-mode twins). The stylesheet is compiled once, at build time, and is identical for every location. Everything that depends on the palette is a CSS custom property, and the browser fills the values in:
LocationSettingsProviderfetches/api/sys/businesses/{id}/colors?mode=light|darkfor the effective colour mode.ThemeTokens(src/theme/ThemeTokens.tsx) computes the tokens withsrc/theme/tokens.tsand writes them into a<style id="nexudus-theme-tokens">at the end of<head>, where they win over the static bundle. That takes about a millisecond, so theme toggles and editor previews (?Theme.PrimaryColor=...) apply instantly.index.htmlcarries atheme-fallbackblock with the default primary colour for the splash screen.ThemeTokensremoves it once the real tokens are in.
Where a branded value can live
src/theme/tokens.ts is the runtime twin of _branding.scss: it computes the same token names with the same formulas, using the Bootstrap colour functions ported in src/theme/color.ts.
Token names
For a theme colour<c> (primary, secondary, success, info, warning, danger, plus the derived $theme-colors entries such as primary-contrast and primary-bg-subtle):
Using a branded colour
Never write$primary (or any tenant colour variable, or a Sass function of one) into a rule. That bakes the default palette into the bundle for every location. Use the token:
tint-color($primary, 25%)), add a token rather than computing it in Sass.
Adding a new token
1
Define it for the default palette in _branding.scss
Add the formula to the
:root block (and to the color-mode(dark) block if it has a dark-mode value):2
Compute it at runtime in src/theme/tokens.ts
Add the same formula to
buildThemeTokens, under the same name, in light (and dark if applicable):src/theme/color.ts has tint, shade, shift, mix, colorContrast, toHex and toRgbTriplet, all ports of the Sass functions Bootstrap uses.3
Use it
var(--bs-primary-tint-25) in any stylesheet or component.4
Run the theme tests
tokens.test.ts compiles the real SCSS with several palettes and fails if a TypeScript value differs from the Sass one. branding.test.ts fails if anything in the bundle still bakes a tenant colour (it names the selector and property), if a runtime token has no static twin, or if a runtime rule targets a selector the bundle does not contain.Making a Bootstrap variable follow the tenant colour
If a Bootstrap variable such as$pagination-hover-bg is only interpolated into CSS, set it to a token in _variables.scss: $pagination-hover-bg: var(--#{$prefix}primary);. If Bootstrap runs tint-color, shade-color, mix or color-contrast on it (buttons, tables, coloured links), leave the variable alone and re-declare the generated rule in _branding.scss on tokens, the way .btn-#{$name} is done. The bundle guard tells you which case you are in: it lists the compiled rule that still changes with the palette.
Colours embedded in SVG data URIs
var() does not work inside url(). Bootstrap embeds $success and $danger in the validation icons and the primary emphasis colour in the accordion chevron. Those are emitted as whole rules by buildThemeTokens (the rules array) with the same selector as Bootstrap’s rule. Add to that list if you introduce another one.
Border radius
Each location also picks a corner style with theTheme.BorderRadius setting (editor previews can pass ?Theme.BorderRadius=...). It takes one of three presets; empty or unknown values mean Default:
_variables.scss derives the scale from $border-radius and seeds the custom properties with the Default preset; ThemeTokens emits the location’s preset from src/theme/radius.ts alongside the colours. Component variables ($btn-border-radius, $input-border-radius, $card-border-radius, …) point at the tokens, so Bootstrap components follow the preset on their own. .btn-close and kbd bake $border-radius through a mixin default and are re-declared in _branding.scss.
Never write $border-radius, $border-radius-lg & co. into a rule: use var(--bs-border-radius), var(--bs-border-radius-lg), or the rounded-* utilities. tokens.test.ts compiles the SCSS with each preset and checks radius.ts against it, and the bundle guard in branding.test.ts names any rule that still bakes a radius.
Dark mode
Dark mode is controlled via thedata-bs-theme attribute on the <html> element. The _dark-mode.scss file redefines CSS custom properties when this attribute is set:
useLayoutContext hook:
localStorage and restored on load.
When writing component styles, always use CSS custom properties (
var(--bs-body-bg)) instead of hard-coded colour values. This ensures your styles work in both light and dark modes.Extending Bootstrap utilities
Custom utility classes are registered incustom/_utilities.scss using Bootstrap’s utility API. To add a new utility:
.custom-opacity-25, .custom-opacity-50, etc. with responsive variants if you add responsive: true.
Overriding Bootstrap component styles
Bootstrap component customisations live incustom/ as partial SCSS files (e.g., _buttons.scss, _card.scss). These are imported after Bootstrap core, so they can override default styles.
To adjust a Bootstrap component:
1
Find the right partial
Look in
src/assets/scss/custom/ for an existing file that matches the component (e.g., _buttons.scss for buttons).2
Add your overrides
Write your SCSS rules in that file. You can use Bootstrap variables and mixins:
3
Verify the import
Make sure the file is imported in
style.scss. All existing partials are already imported.Adding styles to a new component
1
Create a CSS Module file
Add a
.module.scss file next to your component:2
Write scoped styles
3
Import and use in the component
The _user.scss file
_user.scss is imported last in style.scss and is intended for project-level custom rules that don’t belong to a specific component or Bootstrap override. It currently contains helpers like .sticky-top-20, .no-select, and .disabled-component.
Add rules here when you need a global utility that doesn’t fit into Bootstrap’s utility API or a component module.