CSS
Overview
With the styling power that CSS provides as a base technology, it is very easy and tempting to write styles that apply in one situation that should not in another. In line with our architectural goal of composition, we now target component styles using the Block Element Modifier (BEM) naming convention. While this is not foolproof because all styles still reside within the global namespace within a web browser, the BEM convention drastically reduces the likelihood of a style regression caused by naming clashes, specificity battles and the Cascade. We manipulate basic CSS by using SCSS, a superset of CSS that offers features that allow succinct authoring techniques while outputting selectors that appropriately target markup while also allowing extensibility by other developers.
Implementation
SCSS usage
As part of the Tui framework we made the decision to switch our CSS preprocessor over to SCSS. SCSS has become the leading CSS preprocessor and provides us with better build performance.
The existing CSS from the Basis theme has already been converted over to SCSS in the Legacy theme. The Ventura CSS sits on top of the existing legacy styles, but is predominantly CSS variable overrides from core Tui component style definitions.
Key differences between SCSS and LESS
SCSS is an imperative language while LESS is a declarative language - this impacts where and how variables should be defined. Previously with LESS, variables could be used before their declaration and if defined multiple times the last definition was used throughout, while with SCSS the variables must be defined before use.
Global variable
$red: #ff0000;
Chaining/static variables
$red: #ff0000 !default;
Creating a mixin
@mixin box-shadow($shadow) {
box-shadow: $shadow;
}
Calling a mixin
Block Element Modifier (BEM) methodology
We have introduced the BEM methodology as part of the Tui framework.
BEM is a popular CSS naming methodology. The key benefits of using this methodology are that it provides a consistent approach throughout the code base and helps you to manage both inheritance and specificity.
BEM provides elements with unique classes allowing CSS to be targeted at particular use cases. This essentially creates a limited scope where styles are applied, reducing the amount of styles inherited through the cascade. Elements having unique classes also reduces the need to have nested CSS selectors when targeting a particular element, which often leads to issues with specificity.
The following example shows how the BEM class structure has been applied to the checkbox component:
For classes with multiple words we use the camelCase naming convention:
Blocks
A BEM block is a standalone entity that is meaningful on its own. They can be nested but semantically they remain equal. Within Tui we treat each of our Vue components as a block entity.
The block sets the class namespace for all of its inner elements which semantically ties them together. The block name should be descriptive enough to express what it is and unique enough to avoid name conflicts with other blocks. For multi-word block names we use camel case.
Checkbox block entity
The Tui framework has a convention of prefixing each block with tui-
, this should not be used by non-totara components or plug-ins.
Elements
Each block has its own set of unique elements which cannot be used or styled by other blocks and have no stand-alone meaning. Their classes are prefixed with the block class followed by a double underscore.
Sub-elements further extend their parent element class with a dash prefix.
Blocks with several layers of elements usually suggests that a subcomponent is more appropriate.
Modifiers
The modifier is a flag used to modify the appearance, state or behaviour of a block or element. Examples of modifiers include disabled
, active
, large
, small
, hasButton
. The modifiers allow blocks to be customised while keeping the styles self-contained. Modifiers should be used instead of overwriting the styles from outside of the block with specificity.
Modifiers are prefixed with the block class followed by a double dash.
Block modifiers will usually be enabled or disabled by their parent components, within Vue we use standalone boolean props to manage this.
Block
Parent block
Variables
Within the Tui framework we have two types of style variables, SCSS variables and CSS variables.
CSS variables are the preferred approach going forward but there are a number of places where we are required to have static variables such as in CSS media queries.
In Totara 16 and earlier we only support root-level CSS variables to allow for full compatibility with IE11.
Global variables are fetched before the static and component CSS and are included from the following file:
Static CSS
For any styles that can't be generated at a component level or for elements that aren't generated by components we have a static SCSS file. The styles in this file are included before any of the component styles.