Skip to content

SASS and CSS Custom Properties

zack Frazier edited this page Feb 15, 2019 · 5 revisions

Theming approach with SASS and CSS vars

There are two desired approaches for theming, to change the full set of foundation variables to effect a big change in look and feel or to change individual component properties to affect granular changes.

Using SASS variables with the !default flag makes this theming easy to accomplish. Doing the same with CSS custom properties introduces new challenges.

Overview

We use a root.scss file to set high-level global CSS custom properties.

:root {
	--fd-color-action-1: #0a6ed1;
	--fd-color-action-2: white;
	--fd-color-text-1: #32363a;
	--fd-color-text-2: #51555a;
	--fd-color-text-3: #6a6d70;
	--fd-color-text-4: #74777a;
	...
}

This works great for the first type of major theming approach. The SASS maps can be updated and the :root rules are generated. Component styles using the global CSS variables will inherit upstream changes.

However, specificity becomes a problem when setting component-level variables.

:root {
	--fd-button-color: var(--fd-color-custom-1);
}
:host {
	--fd-button-color: var(--fd-color-custom-1);
}

The above will do nothing. The existing custom property set in the button block — .fd-button, [class*=fd-button--] — has a greater specificity.

There are two approaches worth considering.

Generic style attributes

An individual component has certain attributes that can be themed like background, border, etc. Some of these are inherited in CSS by default while others do not, e.g., all elements inherit the body color but not background color.

Custom properties can be set to mimic these CSS properties at a global level which get overridden at the element and component level.

:root {
	//set generic themeable params which all themeable elements will use
	--fd-color-text: var(--fd-color-text-1);
	--fd-color-background: var(--fd-color-background-2);
}
body {
	color: var(--fd-color-text); //inherited by default
	background-color: var(--fd-color-background); //not inherited by default
}
.fd-button {
	--fd-color-text: var(--fd-color-action-1);
	--fd-color-background: var(--fd-color-background);
}
.fd-button--emphasized {
	--fd-color-text: var(--fd-color-action-2);
	--fd-color-background: var(--fd-color-action-1);
}

This approach is easy to understand and implement since it does not rely on unique property names. Authors could easily override by setting the themeable property for any custom element.

.fd-custom-button {
	--fd-color-text: var(--fd-color-accent-2);
}

It could be argued this is the primary intended benefit of custom properties which take great advantage of the inheritance cascade. However, it does not solve the :host issue where the specificity of the class name would still override the element block.

:host {
	--fd-color-text: var(--fd-color-accent-2);
}

The "cascade" part of CSS has been shunned in recent years as devs desire bulletproof styles attached to components yet developers still need configurable styles at the highest and lowest levels. Another approach is needed.

Global theme parameters

Themeable parameters that are exposed globally can be overridden easily in elements and components. A larger set of custom properties could be added to the :root which can be used across all components. This is an accepted practice already when setting up SASS/LESS architectures.

A separation is recommended by base parameters and semantic parameters where the former are used by the latter.

:root {
	//core parameters
	--fd-color-action-1: #0a6ed1;
        --fd-color-action-2: white;
	--fd-color-shell-1: #354a5f;
        --fd-color-shell-2: #d1e8ff;
	--fd-color-background-1: #edeef0;
        --fd-color-background-2: white;
	//semantic parameters
	--fd-brand-color: var(--fd-color-action-1);
	--fd-shell-color: var(--fd-color-shell-1);
	--fd-button-color: var(--fd-color-action-1);
	--fd-button-color-emphasized: var(--fd-color-action-2);
	--fd-button-background-color-emphasized: var(--fd-color-action-1);
}

The semantic parameters should be used in the individual components.

.fd-button {
	color: var(--fd-button-color);
	background-color: var(--fd-button-background-color);
}
.fd-button--emphasized {
	color: var(--fd-button-color-emphasized);
	background-color: var(--fd-button-background-color-emphasized);
}

There are a few concerns with this approach.

  1. CSS variables do not act the same as SASS/LESS variables but they are being used the same way, not taking advantage of inheritance capabilities (good or bad opinions aside).
  2. Exposing variables at a global level increases the risk of misuse where a dev may pick a variable based on the color value and not the semantic value, e.g., a --fd-button-background-color is used for a border.
  3. More global parameters to manage and document.

Recommended

The desired outcome is a theming approach that is as easy as possible to use and to create customizations by either extending the system or creating new components. It is very important that the correct parameters be used when applying the system to an application. A single mis-used parameter can break a theme.

SASS and CSS

The system must clearly define when to use SASS and when to use CSS variables.

Core SASS

SASS should be used for configuration only setting values that can be pre-processed.

  • SASS maps can be used to define high-level foundational themeable core parameters
  • Derived color states can be computed from the core parameters
  • CSS variables can be generated from the SASS config variables
  • Functions and mixins can assist with look-up and repetitive tasks

SASS variables should not be used in element and component CSS.

Semantic CSS variables

CSS variables should be used for global theme parameters that may be modified at runtime.

  • CSS custom properties should be used for component colors, spacing, border radius, etc.
  • PostCSS processing should be used to output an IE11 compatible CSS file

Core params vars, e.g., --fd-color-text-1, should not be used in component CSS but they must be exposed for theming run-time theming purposes.

Theme Parameters

The SAP global theme team would like to align all applications to a standard set of global parameters. See https://wiki.wdf.sap.corp/wiki/pages/viewpage.action?pageId=2017196009

Ideally, a service or npm module will be available to deliver the variables.

:root {
	--sapBrandColor: #0a6ed1;
	--sapHighlightColor: #0a6ed1;
	--sapBaseColor: #fff;
	--sapShellColor: #354a5f;
	--sapBackgroundColor: #fafafa;
	--sapFontSize: .875rem;
	--sapTextColor: #32363a;
	--sapLinkColor: #0a6ed1;
	...
}

Adopting the vars as-is will require component-by-component updates.

.fd-shellbar {
	background-color: var(--sapShellColor);
}

Natural outcomes

If theme parameters are supplied, including derived values, the need for SASS for configuration is becomes less important. Many teams are abandoning SASS and LESS in favor of plain CSS as styles have become more modular.

Theme planning should be focused around evaluating the need for SASS for all variable setting. There are open questions around how implementers will use the library to build custom themes. A central tool will eventually allow for a customer to create a custom theme and export the file or have it available via an API.

In the short-term, it may still be a valid use case for a developer or partner to modify the SASS to build a custom theme. SAP products are built and maintained for many years by developers with a variety of experience and expertise. Decisions do not need to be made for a lowest common denominator but that should be researched and considered.

Use cases

  • As a developer, I want to use the full Fundamentals library in my application to apply consistent Fiori 3 look and feel.
  • As a developer, I want to use the Fundamentals library to encapsulate Fiori 3 styles in custom Angular components.
  • As a developer, I want to use the Fundamentals library to encapsulate Fiori 3 styles in custom React components using CSS modules.
  • As a developer, I want to use the CSS of one component of the library to construct a custom component.

References

https://paulmillr.com/posts/using-dark-mode-in-css/ https://developer.apple.com/documentation/appkit/nscolor https://www.sitepoint.com/css-theming-custom-properties-javascript/ https://www.joshmorony.com/creating-a-theme-switcher-service-in-ionic-using-css4-variables/ https://video.sap.com/media/t/1_n71mdynx https://wiki.wdf.sap.corp/wiki/pages/viewpage.action?pageId=2068428904