Code Quality Help

Styling and Theming

Theming Philosophy

All libraries follow a two‑level CSS custom property cascade that balances global consistency with component‑level flexibility. The system uses a set of global variables (prefixed --grain-) shared across every library, while each component exposes its own scoped variables for fine‑grained overrides. This allows application teams to define a single brand theme (e.g., font family, primary color) that propagates to all library components, yet still tweak a specific slider or input without affecting others.

.my-component { font-family: var(--grain-font-family, var(--slider-font-family, Roboto)); }

Resolution order (highest priority first):

  1. Global variable --grain-font-family – set once in the host application, affects all libraries.

  2. Component variable --slider-font-family – overrides the property for this specific component only.

  3. Hardcoded default value (Roboto) – the fallback when neither variable is defined.

SCSS Architecture per Library

Each library must organize its style sources in a predictable structure.

packages/slider/ src/ lib/ slider.component.scss // component styles using CSS custom properties styles/ _mixins.scss // reusable mixins (optional) ...

Global Variables (--grain-*)

Global variables represent the shared design tokens. They are not defined inside any library – they are expected to be provided by the consuming application. A library never sets a value for a --grain-* variable; it only references it in the cascade.

Default Values

The final fallback in a var() chain is a literal value (e.g., a color hex, a font stack). It must be a sensible standard that matches the design language. All fallback values must be explicitly typed in the SCSS variable documentation.

Writing Component Styles

Component stylesheets must:

  • Never use hardcoded values for properties that are part of the component’s public styling API. Use the cascade pattern instead.

  • Reference only CSS custom properties (via var()) for customizable properties; do not reference SCSS variables directly inside .scss rules because those are compiled away and lose runtime override capability. The SCSS variables exist solely to set the initial value of the CSS custom properties.

  • Use the component's selector when targeting the component’s own root element, and standard class selectors for internal elements.

Example of a fully themed component style:

// slider.component.scss @use '../styles/variables' as *; grain-slider { display: block; font-family: var(--grain-font-family, var(--slider-font-family, Roboto)); } .slider-track { background: var(--grain-neutral-color, var(--slider-track-color, #e0e0e0)); border-radius: var(--grain-border-radius, var(--slider-border-radius, 4px)); } .slider-thumb { background: var(--grain-primary-color, var(--slider-thumb-color, #1976d2)); }

Customization and Overriding

Consuming applications override styles by re‑declaring CSS custom properties at a higher scope (e.g., :root, a theme class, or the host element).

Global override (applies to all libraries):

:root { --grain-font-family: 'Inter', sans-serif; --grain-primary-color: #ff5722; }

Component‑specific override (applies only to sliders):

.my-slider-wrapper { --slider-track-color: #bdbdbd; }

Libraries must not force a particular theming approach (e.g., by scoping variables under a class name only). The cascade works because the component itself reads the variable; any ancestor in the DOM tree can set it.

Prohibited Practices

  • Hardcoded values: Directly writing font-family: Roboto; or color: #333; in a component style is forbidden. All appearance‑related values must go through a CSS custom property cascade.

  • !important: Using !important is banned (enforced by Stylelint). If a style conflict arises, specificity should be resolved through proper cascade design, not brute force.

  • Deeply nested overrides: Targeting internal elements of another library from application styles using complex selectors is discouraged. Use the provided CSS custom properties instead.

  • Missing fallbacks: Every var() must include a final fallback value (or chain to a global variable that ultimately has a fallback). A broken var() call with no fallback may result in properties being set to initial, which is unpredictable.

  • Mixing SCSS variables and CSS custom properties in a single value in a way that breaks runtime customization. The only SCSS variables inside component rules should be inside #{...} interpolation used to initialize custom properties. Do not compile static values into the rule if they are meant to be overridable.

Style Validation and Testing

Linting

Stylelint verifies that:

  • No !important exists.

  • Property ordering follows the defined convention.

Visual Testing in Storybook

Every component’s Storybook stories must include a “Themed” or “Custom Properties” panel (using addons like @storybook/addon-themes or custom controls) that allows testers to dynamically modify global and component‑level variables. The Definition of Done requires that a story demonstrates at least one custom property override in action, proving the theming mechanism works.

03 July 2026