Creating custom themes

Overview

While the base technologies used within Tui-based themes have changed, a number of theme features from older versions of Totara are still available. Totara 13's new Ventura theme provides a variable and override approach for other custom themes that inherit from Ventura. Ventura itself inherits the new Legacy theme, which provides a pathway to migration away from older technology, while retaining support for Totara Learn and applying a level of visual consistency with Totara Perform and Totara Engage.

Implementation

Custom theme inheriting from Ventura and Legacy

To create a new theme targeting the new features in Totara 13, you will need to create files in both the server and client directories. The naming convention for your custom theme must follow these rules:

  1. The name should start with one or more lowercase alpha characters (a-z).

  2. If there are any underscores:

    1. All characters before the first underscore need to be lowercase alpha characters (a-z).

    2. The character directly following the first underscore needs to be lowercase alpha (a-z), the following characters can be any combination of underscores and lowercase alphanumeric values.

  3. The name should end with lowercase alphanumeric values (a-z, 0-9).

The files you'll need are:

server/theme/mytheme/config.php

<?php $THEME->doctype = 'html5'; $THEME->name = 'mytheme'; $THEME->parents = ['ventura', 'legacy', 'base']; $THEME->enable_dock = true; $THEME->enable_hide = true; $THEME->minify_css = false;

server/theme/mytheme/lang/en/mytheme.php

<?php $string['pluginname'] = 'My Theme'; $string['choosereadme'] = 'My Theme is a custom theme for Totara.'; // Block positions. $string['region-bottom'] = 'Bottom'; $string['region-main'] = 'Main'; $string['region-side-post'] = 'Right'; $string['region-side-pre'] = 'Left'; $string['region-top'] = 'Top';

server/theme/mytheme/version.php (copy from the Ventura version.php and update the version and component fields)

If you want to enable the Theme Settings page for your custom theme, you'll also need to copy the following files from Ventura and update them to match your theme's name:

  • index.php

  • lib.php

  • settings.php

That takes care of the files in the server directory, but if we want to apply customisations to the Tui styling or components, we also need to create a folder in the client/component directory with the right files.

This can be done in one step by running npm run tui-init theme_mytheme vendor (where vendor is a unique string identifying your organisation. Lowercase is suggested for ease of typing - Totara internal components use 'totara' for example).

At this point you're now ready to start customising.

In order to customise component CSS, template, or functionality, you can add a component file in a special location where it will be picked up by the override system. Refer to the Totara 13 Theme stack diagram for the high-level flow of inheritance and overrides.

Custom theme inheriting from Legacy only

Follow the same steps as above, but remove 'ventura' from the array in config.php.

Customising variables and global CSS

After completing the instructions above, you can add your theme customisations in the following places:

  • client/component/my_theme/global_styles/_variables.scss (variable overrides)

  • client/component/my_theme/global_styles/static.scss (global CSS rules)

You can also break up your variables or global CSS rules into multiple files and use the @import syntax to include them. @import 'my_theme/xyz' maps to client/component/my_theme/global_styles/xyz. You can see this in action at client/component/tui/src/global_styles/_variables.scss.

Overriding component styles or functionality

Any Vue component can be extended in a theme by creating a file in the right folder.

The pattern for override files is: client/component/theme_(themename)/src/(subfolder)/overrides/(original client/component/* subfolder)/(rest of path)

For example, if the component you want to extend is: tui/components/buttons/Button, you would create client/src/theme_mytheme/components/overrides/tui/buttons/Button.vue

This component will replace the original one, unless it only contains a <style lang="scss"> block, in which case the original component will continue to be loaded.

In T13-T18, partial component overrides were possible. In T18 these were deprecated.

Modifying SCSS

To customise the styling, add a new <style lang="scss"> block. Styles defined here are in addition to styles defined in the original component, they do not replace them.

Modifying template

To modify the template, add a <template> block to the override file. This will replace the original template, but the original logic will remain and you will get access to any data or methods exposed by the component.

Modifying JavaScript

It's also possible to replace the logic of the component by adding a <script> block to the override file. This will replace the entire component definition however, so you must define your own template and strings blocks. Styles will still apply.

If you want to use a component in your custom template that was not used in the original template, you'll need to override the script block as well to add the component to the 'components' object. Normally this would entirely replace the original script block, but you can use <script extends> to instead define a component that extends the original JS, retaining all its functionality. You should only use this to add components to the definition, if you want to extend the functionality, you should define a regular script block.

<script extends> import Popover from 'tui/components/popover/Popover'; export default { components: { Popover, }, }; </script>

Modifying strings

You can request extra strings in the overrides by defining a <lang-strings> block. The content of this block will be merged with the original component.

It's recommended to list any strings you're using in the template here even if they are already specified in the base component, as an update to the original may remove strings that are not in use by the original's template.

Tips

  • When adding a <script> block to an overridden component, this will disable inheritance - both the script and the template will not be inherited from the parent component. In order to keep inheriting, add an extends attribute to the script tag: <script extends>. This is mostly useful for adding new components to the 'components' object. If you do this, as per usual do not call any private methods on the component you are inheriting from - this is not a supported use case.

  • Overriding individual code blocks within a Single File Component does carry a maintenance risk, as the underlying Vue component you are overriding may be deprecated. Read our deprecation guidelines for some more information on this use case.

Variable usage

In general, variables used by components are defined in Tui core or plugins and can then be overridden by themes.

CSS variables may freely depend on the value of other variables, and updating the value of a base variable will change the computed value of variables that depend on it as well.

Both CSS variables and SCSS variables are available, but it is strongly recommended to use CSS variables as they work a lot better for theming - chains of CSS variables do not pose a problem, whereas they do in SCSS due to its imperative nature.

Note that code in a Tui component may not depend on variables defined in another Tui component, unless that component is declared as a dependency in tui.json.

Derived variables

Some variables within the Ventura theme are preceded by a comment when they are defined in src/global_styles/_variables.scss. These comments denote that the variables will be processed when a change is made to the file. We process these variables to output a JSON object which describes the variables and their relationship to one another. The JSON output for a Tui-based theme can be found in client/component/theme_customthemename/build/css_variables.json. For example, the following variable definition has two preceding comments:

The first, /* theme:var */, tells the processing script to process the variable. The second, /* theme:derive adjust-hex-value-brightness(var(--color-state), -10) */, is additional data to be used during the process step.

In this example, the hover colour value for the variable --color-state-hover will be calculated by a script in tui/theme, specifically the method adjust-hex-value-brightness() will be called, using the variable --color-state as a starting point and adjusting the brightness value by -10.

Processing variables in this way provides a replacement for the now unsupported SCSS functions (Darken(), Lighten(), mix(), button-variant() etc.), as they do not operate on CSS Variables.

It also provides a way to reduce the number of colour options expressed in the Theme Settings page, and have them automatically re-processed with Javascript when a base colour is changed. This removes the need for the SCSS compiler to be invoked as there is no need to re-call a SCSS function such as Darken().

The JSON object that is output is then consumed by the Theme Appearance page and used to populate field values.

Tui-based custom themes that add these comments above variables defined in _variables.scss will also result in a JSON object being output for that theme. The custom theme could then express any new or altered variables in a Theme Appearance page Vue SFC file, therefore customising the available UI options for the custom theme.

CSS load order

CSS is included in the following order:

  1. Legacy CSS.

  2. SCSS variables in the same order as below (typically not present as we prefer CSS variables for their greater flexibility).

  3. CSS variables and CSS from Tui core.

  4. CSS variables and CSS from each Tui component.

  5. CSS variables and CSS from each Tui theme.

  6. Theme customisations via UI - CSS variable overrides.

  7. Theme customisations - user-defined custom CSS.

Tips and known limitations

  • CSS class names - as with custom components, if you are defining an entirely new component it is recommended that you use a prefix other than tui- in the class names to avoid collisions.

  • If you are entirely replacing a component (new template, style, and script), you will probably find it easier to use a new CSS class name to avoid collisions with the old styling.

  • Overriding TUI Core or other TUI Plugin code blocks from inside another plugin other than a theme is not currently supported, due to the risk of third-party plugins detrimentally overriding code.

  • When using a custom theme no customisations of the parent theme settings are applied to the custom theme. Child themes inherit only from the default parent theme settings.

Recommended reading

Totara publishes an Example Theme to demonstrate the structure of a custom theme, including both features from the Tui framework, and features from earlier versions of Totara (useful when upgrading your custom theme to use Tui).