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
languagexmltitleGlobal variablesass
$red: #ff0000;
Chaining/static variables
Code Block
languagexml
titleChaining/static variables
sass
$red: #ff0000 !default;
Creating a mixin
Code Block
languagexmltitleCreating a mixinsass
@mixin box-shadow($shadow) {
	box-shadow: $shadow;
}
Calling a mixin
Code Block
languagexml
titleCalling 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
languagexmltitleCheckbox block entityhtml
<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
title
languagexmlhtmlBlock
  <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.

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.

Currently 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:

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

...

The Tui framework does aim at style isolation, however it does not make use of real isolation via the Shadow DOM. This particular feature is was not relied upon initially used because we require required support for IE11, which does not support the Shadow DOM. There is an alternative available within Vue SFCs, 'scoped styles', however we also have not adopted this approach which generates a unique identifier and applies that to selectors. The reason we have not adopted this approach is because of style inheritance across themes - if there is a unique identifier on a given selector then a child theme wanting to override that selector would need to also somehow know about the unique identifier.

...

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