Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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 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.

...

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
Code Block
languagexmlsass
titleGlobal variable
$red: #ff0000;
Chaining/static variables
Code Block
languagexml
titleChaining/static variables
sass
$red: #ff0000 !default;
Creating a mixin
Code Block
languagexml
titleCreating a mixin
sass
@mixin box-shadow($shadow) {
	box-shadow: $shadow;
}
Calling a mixin
Code Block
languagexmltitleCalling a mixin
@include box-shadow($shadow);

Block Element Modifier (BEM) methodology 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:

Code Block
languagexmlhtml
<div class="tui-checkbox tui-checkbox--large">
	<input class="tui-checkbox__input" id="uid-1" type="checkbox" value=""/>
	<label class="tui-checkbox__label" for="uid-1">OK</label>
</div>

For classes with multiple words we use the camelCase naming convention:

Code Block
languagexmlhtml
<div class="tui-checkboxGroup">
	<input class="tui-checkboxGroup__inputBox" id="uid-1" type="checkbox" value=""/>
	<label class="tui-checkboxGroup__label" for="uid-1">OK</label>
</div>

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
Code Block
languagexml
titleCheckbox block entity
html
<div class="tui-checkbox">...</div>


Info

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.

Code Block
languagexmlhtml
<div class="tui-checkbox__input">
<div class="tui-checkbox__label">

Sub-elements further extend their parent element class with a dash prefix.

Code Block
languagexmlhtml
<div class="tui-checkbox__label-text">
<div class="tui-checkbox__label-icon">

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.

Code Block
languagexmlhtml
<div class="tui-checkbox tui-checkbox--large">
<div class="tui-checkbox tui-checkbox--disabled">

Block modifiers will usually be enabled or disabled by their parent components, within Vue we use standalone boolean props to manage this.

Block
Code Block
languagexmlhtml
titleBlock
  <div
    class="tui-checkbox"
    :class="{
      'tui-checkbox--large': large,
    }"
  >


Parent block
Code Block
languagexmltitleParent blockhtml
<Checkbox :large="true"/>

Variables

Within the Tui framework we have two types of style variables, SCSS variables and CSS variables.

...

Global variables are fetched before the static and component CSS and are included from the following file:

Code Block
languagexmlhtml
client/component/tui/src/global_styles/_variables.scss

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.

Code Block
languagexmlhtml
client/component/tui/src/global_styles/static.scss

Importing SCSS

To import more .scss files into static.scss, use the system component-based frankenstyle import paths (for example @import 'theme_customtheme/oldstyles/totara/nav';), rather than relative importing, which is not supported at this time.

...

SCSS uses 2 space indentation instead of 4 space and rule ordering was introduced for the CSS to keep the code more consistent. We enforce this with Prettier during the development and integration process.

Sizing and helpers

Generally, all sizing is derived from the root font size. In Totara 19 and later, this is the browser default, equivalent to 16px.

Most other sizes in Totara are derived from the root font size, via the rem unit.

You can convert a pixel value to the equivalent rem value using the rem-px helper. For example, rem-px(32) evaluates to 2rem.

Expand
titleTotara 18 and earlier

In Totara 18 and earlier, the root font size is 10px and these helpers are not available, as the math to convert to rem is very simple.

Tips and known limitations

...

Because of our ongoing dependency on the Legacy theme, which styles parts of the UI such as the primary navigation and footer, we still have globally scoped Bootstrap and Legacy-based far-reaching styles battling for dominance with Tui framework specifics. We could not rely on the CSS value unset because that too is not supported in IE11, therefore, Tui components provide the overrides they need to operate in isolation.

...

As part of the switch to SCSS several of the existing methods are no longer supported. Any methods that manipulate a passed non-predefined value such as a CSS variable will no longer work, this includes the following: Darken(), Lighten(), mix() & button-variant().

Recommended reading