CSS / SCSS Guidelines
Architecture overview
Cynosure component styles follow a token-first, utility-first approach:
- Design tokens — exported from Figma as CSS custom properties; consumed everywhere
- Bootstrap 5 utilities — primary layout and spacing tool; write custom SCSS only for things Bootstrap cannot do
- Component SCSS — scoped with the
c-BEM prefix; never leaks outside the component
The SCSS entry point is src/handoff/sass/main.scss. Handoff automatically injects this into every component's compiled CSS. Do not @import or @use it inside a component's style.scss.
Design tokens
Design tokens are exported from the Figma project (3GcQn3eA8Kg9kprYXBXksv) and compiled to CSS custom properties at build time. Token CSS files are available at:
/api/component/tokens/css/colors.css
/api/component/tokens/css/effects.css
/api/component/tokens/css/typography.css
/api/component/tokens/css/button.css
/api/component/tokens/css/alert.css
/api/component/tokens/css/badge.css
/api/component/tokens/css/input.css
/api/component/tokens/css/modal.css
/api/component/tokens/css/select.css
/api/component/tokens/css/tooltip.cssKey color tokens
1/* Brand */
2--color-primary-orange: #ff4713
3--color-primary-orange-light: #ff6c42
4--color-primary-orange-dark: #cc390f
5--color-secondary-purple: #6f2eff
6--color-secondary-purple-light: #8c58ff
7--color-secondary-purple-dark: #5925cc
8--color-tertiary-pink: #db3381
9--color-tertiary-plumb: #361f50
10
11/* Neutrals */
12--color-neutral-gray-100: #f4f4f4
13--color-neutral-gray-300: #d0d2d3
14--color-neutral-gray-600: #696969
15--color-neutral-gray-900: #231f20
16--color-neutral-white: #ffffff
17--color-neutral-black: #000000
18
19/* Text */
20--color-text-hard: #000000
21--color-text-base: rgba(0, 0, 0, .8)
22--color-text-soft: #555555
23--color-text-muted: rgba(85, 85, 85, .8)
24
25/* Text on dark backgrounds */
26--color-text-hard-invert: #ffffff
27--color-text-base-invert: rgba(255, 255, 255, .85)
28--color-text-muted-invert: rgba(255, 255, 255, .45)
29
30/* Borders */
31--color-border-base: rgba(105, 105, 105, .15)
32--color-border-dark: rgba(105, 105, 105, .3)
33--color-border-invert: rgba(208, 210, 211, .2)
34
35/* Gradients */
36--color-gradient-primary: linear-gradient(90deg, #ff4713 0%, #dc001b 100%)
37--color-gradient-energy-blends: linear-gradient(90deg, #db3381 0%, #361f50 100%)
38--color-gradient-hero-dark: linear-gradient(90deg, #434a50 0%, #231f20 100%)
39
40/* Status */
41--color-extra-success: #49c781
42--color-extra-warning: #e6e448
43--color-extra-danger: #f25454
44--color-extra-info: #c0e1f0Key typography tokens
1/* Font families */
2--font-family-juana: 'Juana'
3--font-family-open-sans: 'Open Sans'
4
5/* Heading scale */
6--typography-heading-1-font-size: 48px
7--typography-heading-1-font-family: 'Juana'
8--typography-heading-1-font-weight: 400
9--typography-heading-1-line-height: 1.4
10
11--typography-heading-2-font-size: 36px
12--typography-heading-2-font-family: 'Juana'
13--typography-heading-2-font-weight: 400
14
15--typography-heading-3-font-size: 28px
16--typography-heading-4-font-size: 24px
17--typography-heading-5-font-size: 20pxKey effect tokens
1--effect-shadow-base-300: 0px 1px 2px rgba(0, 0, 0, .15)
2--effect-shadow-base-500: 0px 2px 8px rgba(0, 0, 0, .1)
3--effect-shadow-base-700: 0px 4px 4px rgba(0, 0, 0, .25)Always use CSS custom properties — never hardcode hex values or shadows in component SCSS.
1// Good
2.c-card {
3 background: var(--color-neutral-white);
4 border-top: 3px solid var(--color-primary-orange);
5 box-shadow: var(--effect-shadow-base-500);
6 color: var(--color-text-base);
7}
8
9// Bad
10.c-card {
11 background: #ffffff;
12 border-top: 3px solid #ff4713;
13 box-shadow: 0px 2px 8px rgba(0, 0, 0, .1);
14 color: rgba(0, 0, 0, .8);
15}BEM naming with the c- prefix
All Cynosure component classes use the c- prefix combined with BEM structure:
Examples from the codebase:
1.c-accordion { ... } // Block
2.c-accordion__toggle { ... } // Element
3.c-accordion__panel { ... } // Element
4
5.c-header { ... } // Block
6.c-header__bottom { ... } // Element (sticky nav strip)
7.c-header__top { ... } // Element
8
9.c-select { ... } // Block
10.c-select__dropdown { ... } // Element (Select2 dropdown CSS class)
11.c-select--gray { ... } // Modifier
12
13.c-provider-finder { ... } // Block
14.c-provider-finder__select { ... } // Element
15.c-provider-finder__counter { ... } // ElementKeep class names scoped to the component — never write global element selectors (h2 { ... }) inside a component style.scss.
Path aliases
The Vite build exposes two path aliases for importing assets inside SCSS:
1/* @public → src/handoff/public/ */
2@use '@public/fonts/juana/Juana-Regular.woff2';
3
4/* @export → src/handoff/exported/3GcQn3eA8Kg9kprYXBXksv/ */
5@use '@export/tokens/css/colors.css';These are defined in handoff.config.js:
1resolve.alias['@public'] = path.resolve(__dirname, 'public');
2resolve.alias['@export'] = path.resolve(__dirname, 'exported', '3GcQn3eA8Kg9kprYXBXksv');Component scoping
Never @import, @use, or @forward main.scss inside a component style.scss. Handoff automatically injects the shared stylesheet during compilation.
1// Good — component-scoped styles only
2.c-hero-basic {
3 position: relative;
4 overflow: hidden;
5}
6
7.c-hero-basic__media {
8 aspect-ratio: 16 / 9;
9}
10
11// Bad — do not re-import the shared bundle
12@use '../../../sass/main.scss'; ✗
13@import '../../../sass/main'; ✗SCSS tooling
| Library | Purpose |
|---|---|
| sass-mq | Media query mixins with named breakpoints (sm: 576px, md: 769px, lg: 1254px) |
| modular-scale | Typographic scale functions |
| Normalize.css | CSS reset (included in main.scss) |
| Bootstrap SCSS | Utility classes, grid, component base styles |
Use the sass-mq breakpoints defined in handoff.config.js:
1// Named breakpoints matching handoff.config.js
2// sm: 576px md: 769px lg: 1254px
3
4@include mq($from: md) {
5 .c-hero-basic__headline {
6 font-size: var(--typography-heading-1-font-size);
7 }
8}
9
10@include mq($until: sm) {
11 .c-hero-basic__cta-group {
12 flex-direction: column;
13 }
14}SCSS syntax rules
- Prefer
@useand@forwardover@importin new code (Sass modules) - Keep nesting to a maximum of 2 levels — do not nest BEM elements inside other BEM elements
- Prefer Bootstrap utility classes for spacing, flex, and text alignment; write custom SCSS only when Bootstrap cannot express the design
- Never use
!importantunless overriding a third-party vendor style (Slick, Select2, Magnific Popup) - Use
:focus-visiblefor focus styles — not:focusalone - Add
@media (prefers-reduced-motion: reduce)handling for any transition or animation
1.c-hero-basic__image {
2 transition: transform 0.3s ease;
3
4 @media (prefers-reduced-motion: reduce) {
5 transition: none;
6 }
7}Linting
- Run Stylelint before committing
- Run Prettier with the SCSS formatter for consistent formatting
- Follow the project's Stylelint config for property ordering and selector conventions
On This Page