Skip to content

Latest commit

 

History

History
77 lines (59 loc) · 4 KB

Styles.md

File metadata and controls

77 lines (59 loc) · 4 KB

Styles

Layout

styles/
  main.scss        entry: lists the stylesheets that emit CSS, in cascade order
  abstracts/       Sass variables, mixins and functions; must not emit CSS
  base/            global CSS: reset, grid, elements, helper classes
  images/          images used by stylesheets
  themes/<theme>/  theme.scss, the theme's custom property values
app/component/**   component stylesheets, next to their components

All themes share one stylesheet. Most component styles are listed in styles/main.scss, because they use global class names and their order matters. This is legacy: we're moving to CSS Modules, so don't add component stylesheets to main.scss.

Where things go

What Where
Styles for a new component Component.module.scss next to it, imported by the component
Styles for an existing component Its current stylesheet, or move it to a module when reworking it
Global element or helper styles styles/base/
A value that differs per theme A custom property, see Themes
A value shared by stylesheets, the same in all themes Sass variable in styles/abstracts/_variables.scss
A value used by one stylesheet Local variable at the top of that file
A shared mixin styles/abstracts/_mixins.scss
A page-level z-index $global-zindex in styles/abstracts/_zindex.scss, see Z-Index
Third-party CSS @import … layer(vendor) in styles/main.scss

Every stylesheet that needs shared values starts with a relative @use '../../styles/abstracts' as *;, so update the path when moving a stylesheet. Because of as *, a top-level $name: … with a name abstracts defines changes the shared value for later stylesheets, so pick another name for local variables.

Import a module with import styles from './Component.module.scss'; and read class names as written, e.g. styles['route-name']. Webpack hashes them. Vitest doesn't compile Sass and returns a class name for any key, so a test can import the module and check toHaveClass(styles['route-name']), but it won't catch a misspelled name. Prefer testing what the user sees, as in Tests.

Cascade layers

The layer order is declared once, at the top of styles/themes/default/theme.scss, which loads first:

@layer reset, vendor, tokens, theme;

Later layers win regardless of specificity, and unlayered CSS beats every layer:

  • reset: element defaults (styles/base/_reset.scss).
  • vendor: third-party CSS, currently Leaflet.
  • tokens: default values from @hsl-fi/design-system-core.
  • theme: the values in each theme.scss.
  • Unlayered: the app's own CSS. Avoid !important, and keep it out of layers, where it inverts the order.

Sass or custom property

Use a custom property for values that vary by theme or at runtime, and a Sass variable for everything else. var() doesn't work in media queries or data URIs, and Sass color functions can't take it.

Removing or changing CSS

A class is only unused if it appears nowhere at runtime, and it can come from more places than className:

Source Example How to check
JS, configs, translations, SVGs cx() keys, class="…" in icons and HTML strings git grep -nw <name>, which searches every tracked file
Built names `message-bar-${type}`, lowercased enums, react-select's classNamePrefix Search the prefix or suffix and find the values it can take
Libraries at runtime Leaflet's leaflet-popup-scrolled After yarn build, grep -lw <name> _static/js/*.js, then read each match. Find what triggers it, such as a prop
Other stylesheets @extend git grep -nw <name> -- '*.scss'
HTML from outside the repo CMS banners, third-party scripts Can't be searched, so ask the content's owner

The same goes for changing a rule: narrowing a selector or removing !important can make it lose to another rule. To see what a change did, compare the compiled CSS per theme, then computed styles in a browser.