SVG icons

Overview

Tui uses SVG icons wrapped in Vue components. The icons are defined as SVG files named according to appearance (e.g. chevron-left), which are then wrapped in a Vue component with a semantic name (e.g. BackArrow).

Implementation

The SVG files are automatically optimised with SVGO as part of the build process, and pass through a Webpack loader that converts them to a form that can be understood by JavaScript.

We then have a simple function to generate a Vue component for that icon.

Examples

Icons can be imported and used like so:

<template>
  <div>
    <BackArrowIcon />
  </div>
</template>

<script>
import BackArrowIcon from 'tui/components/icons/BackArrow';

export default {
  components: { BackArrowIcon },
};
</script>

If the icon does not have a built-in colour, so it will be taken from the current text colour.

Icons default to a size of "200" (=16px). You can pass a different value to the size prop in order to get a differently sized icon (100 to 700, in increments of 100), or null to size it with the text. You should also pass an "alt" attribute to the icon where it is needed for accessibility.

For icons defined in Tui, you can see a list of them all on the Tui samples page under icons/Common.

Defining a new icon in a plugin

First you'll need an SVG file. It needs to be placed under icons/internal/obj in order to work. If adding a custom SVG file make sure fill and stroke are set to currentColor so it gets the right colour when using it.

Icon wrappers should go in client/component/my_plugin/src/components/icons/ and be named according to their semantic meaning, not appearance (e.g. Delete.js, not TrashBin.js).

Create a file like so:

Add.js
// import an svg from bootstrap-icons in tui
import icon from '../../../tui/src/icons/internal/obj/bootstrap-icons/plus.svg';
// or one in your plugin
import icon from './internal/obj/bootstrap-icons/plus.svg';
import { createIconComponent } from 'tui/lib/svg_icon';

export default createIconComponent(icon);

Defining a new icon in Tui core

First you'll need an SVG file. It needs to be placed under icons/internal/obj in order to work. There are a lot under bootstrap-icons, and Totara original icons should be placed in the totara folder. If adding a custom SVG file make sure fill and stroke are set to currentColor so it gets the right colour when using it.

Icon wrappers should go in client/component/tui/src/components/icons/ and be named according to their semantic meaning, not appearance (e.g. Delete.js, not TrashBin.js).

Create a file like so:

Add.js
import icon from './internal/obj/bootstrap-icons/plus.svg';
import { createIconComponent } from 'tui/lib/svg_icon';

export default createIconComponent(icon);

Including icons in the font library

We have used a web application called icomoon to create our fonts from SVG icons. The sources can can be found in server/theme/base/fonts/roots_font.json.zip (for the deprecated Roots/Basis theme stack) and server/theme/base/fonts/tfont.json.zip (for the newer Legacy/Ventura) theme stack. Once icons have been added and the font is regenerated, add the outputted font files into the server/theme/base/fonts directory (renaming as necessary) and the resulting SCSS files in the appropriate server/theme/base/scss/icons directory (additional naming changes may need to be done in style.scss).

Custom theme updates

Any custom themes using the following line of code will need to remove it, otherwise icons will not load. This potentially includes any custom theme that was based on Roots or Basis:

$THEME->parents_exclude_sheets = array(
    'base' => array('flexible-icons'), // Delete this line
);

You can find instructions on how to use the font icons in the Flexible Icons API documentation.

Tips and known limitations

  • Try to avoid any explicitly set colours as these will not update with the theme. Fill and stroke should be currentColor rather than a hex value.
  • If you need another colour, the best option is probably to add a class to the element in question and style it through CSS. The best place to put the CSS would probably be to make the wrapper file a Vue file instead of a JS file (leave out the template) and add a style block.
  • For consistency when injecting into a font library avoid using strokes (use fills instead) and masks (draw multiple objects instead).