The Members Portal uses Bootstrap 5.3 with SCSS customisation, CSS Modules for component-scoped styles, and CSS custom properties for runtime theming. This page explains how these layers work together and how to make changes at each level.
Architecture overview
┌──────────────────────────────────────────────────────┐
│ Runtime branding colours (per-location) │ ← Highest priority
│ Injected via /api/scss-to-css endpoint │
├──────────────────────────────────────────────────────┤
│ _user.scss │ ← Project-level overrides
├──────────────────────────────────────────────────────┤
│ Component styles (.module.scss + global SCSS) │
├──────────────────────────────────────────────────────┤
│ custom/ — Bootstrap component extensions │
├──────────────────────────────────────────────────────┤
│ _variables.scss — Theme colour palette │
├──────────────────────────────────────────────────────┤
│ _defaults.scss — Base colour defaults │
├──────────────────────────────────────────────────────┤
│ Bootstrap 5.3 core │ ← Lowest priority
└──────────────────────────────────────────────────────┘
Styles are compiled into a single CSS bundle. The compilation order in style.scss determines which layer can override which.
Folder structure
All SCSS source files live under src/assets/scss/:
src/assets/scss/
├── style.scss # Main entry – imports everything in order
├── _defaults.scss # Default colour palette ($primary, $secondary, …)
├── _variables.scss # Derived theme colours, contrast values, CSS vars
├── _variables-dark.scss # Dark-mode variable overrides
├── _dark-mode.scss # Dark-theme component rules
├── _mobile.scss # Mobile-only responsive overrides
├── _user.scss # Project-level custom rules (safe to edit)
├── icon.css # Icon font definitions
├── custom/ # Bootstrap component extensions
│ ├── _utilities.scss # Extra utility classes
│ ├── _buttons.scss
│ ├── _card.scss
│ ├── _navbar.scss
│ ├── forms/
│ │ ├── _form-check.scss
│ │ └── _form-control.scss
│ └── …
└── components/ # Global component styles
├── _general.scss
├── _avatar.scss
├── _navbar-mobile.scss
├── _sidebar-admin.scss
├── _utilities.scss
├── _stepper.scss
└── vendor/ # Third-party library overrides
├── dayPicker.scss
├── flatpickr.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:
// _defaults.scss
$primary: #ff5b13 !default;
$secondary: #0f0d76 !default;
$success: #008d42 !default;
$danger: #dc2626 !default;
$warning: #da7500 !default;
_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.
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.
// ArkSmallCard.tsx
import styles from './ArkSmallCard.module.scss'
export default function ArkSmallCard({ image }: Props) {
return <img className={styles.fullImage} src={image} />
}
// ArkSmallCard.module.scss
.fullImage {
height: calc(100% - 100px);
object-fit: cover;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
Use CSS custom properties inside .module.scss files to reference theme colours:
color: var(--bs-primary);
Bootstrap utility classes
Most layout and spacing is handled with Bootstrap 5 utility classes directly in JSX:
<div className="d-flex justify-content-between align-items-center mt-3 px-4">
<h5 className="text-truncate fs-5 fw-bold text-body">{title}</h5>
</div>
The project extends Bootstrap’s utility API in 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:
import './Typewriter.scss'
Prefer CSS Modules over global imports for new components. Global styles can cause unintended side-effects.
Runtime branding (per-location colours)
Each location can set its own brand colours in the Nexudus dashboard. The portal loads these at runtime through a two-step process:
- The client fetches
/api/scss-to-css?businessId=<id>&hash=<colorHash>.
- The endpoint compiles a virtual SCSS file that overrides
$primary, $secondary, etc. before importing the full style.scss.
- The compiled CSS is cached in Vercel Blob storage with a hash-based filename.
- An edge function serves the cached file with CDN headers for fast delivery.
The virtual SCSS that gets compiled looks like this:
$runtime: production;
$primary: #<locationColor>;
$secondary: #<locationColor>;
// … other overrides from the location's colour settings
@import 'style.scss';
Because _defaults.scss uses !default, the runtime values take precedence.
Dark mode
Dark mode is controlled via the data-bs-theme attribute on the <html> element. The _dark-mode.scss file redefines CSS custom properties when this attribute is set:
[data-bs-theme='dark'] {
--bs-body-bg: #222529;
--bs-body-color: #b0b0b8;
--bs-border-color: rgba(255, 255, 255, 0.07);
// … full dark palette
}
Theme switching is managed by the useLayoutContext hook:
const { updateTheme } = useLayoutContext()
updateTheme('dark') // 'light' | 'dark' | 'auto'
The preference is saved to 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 in custom/_utilities.scss using Bootstrap’s utility API. To add a new utility:
// custom/_utilities.scss
$utilities: map-merge(
$utilities,
(
"my-custom-util": (
property: opacity,
class: custom-opacity,
values: (
25: .25,
50: .5,
75: .75,
)
),
)
);
This generates classes like .custom-opacity-25, .custom-opacity-50, etc. with responsive variants if you add responsive: true.
Overriding Bootstrap component styles
Bootstrap component customisations live in custom/ 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:
Find the right partial
Look in src/assets/scss/custom/ for an existing file that matches the component (e.g., _buttons.scss for buttons).
Add your overrides
Write your SCSS rules in that file. You can use Bootstrap variables and mixins:// custom/_buttons.scss
.btn {
border-radius: 0.5rem;
font-weight: 600;
}
Verify the import
Make sure the file is imported in style.scss. All existing partials are already imported.
Adding styles to a new component
Create a CSS Module file
Add a .module.scss file next to your component:src/components/MyComponent/
├── MyComponent.tsx
└── MyComponent.module.scss
Write scoped styles
// MyComponent.module.scss
.wrapper {
padding: 1rem;
border: 1px solid var(--bs-border-color);
border-radius: var(--bs-border-radius);
background: var(--bs-body-bg);
}
Import and use in the component
import styles from './MyComponent.module.scss'
export default function MyComponent() {
return (
<div className={styles.wrapper}>
<p className="text-body fs-6 mb-0">Content</p>
</div>
)
}
Combine CSS Modules for component-specific layout with Bootstrap utilities for spacing and typography. This keeps styles scoped while reusing the design system.
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.
Key conventions
| Practice | Rationale |
|---|
| Use CSS Modules for new components | Prevents style leaking between components |
Use var(--bs-*) for colours | Ensures light/dark mode and branding compatibility |
| Never hard-code colour values in components | Runtime branding would not apply |
Put Bootstrap overrides in custom/ | Keeps overrides organized and discoverable |
Put vendor/library overrides in components/vendor/ | Separates third-party concerns from project styles |
Use !default for new SCSS variables | Allows runtime and theme-level overrides |