The current icon system in WVUI is very different from the one in OOUI. They have different benefits and drawbacks that we should take into account when deciding on how we want to use icons in Vue going forward.
OOUI's icon system
Delivery and use
Icons are stored in individual .svg files. They are embedded in CSS that looks like this (simplified):
.oo-ui-icon-alert { background-image: url('data.image/svg+xml,<svg><path d="..." /></svg>'); }
Callers can use an icon by passing in the icon name as the icon config option to an IconWidget, or to any widget that uses the IconElement mixin, e.g.:
var button = new OO.ui.ButtonWidget( { icon: 'alert' } );
which generates HTML that uses the CSS class for the icon:
<span class="oo-ui-iconElement-icon oo-ui-icon-alert"></span>
Icons are grouped together in bundles that fit together thematically, with each bundle being a ResourceLoader module (e.g. oojs-ui.styles.icons-alerts, oojs-ui.styles.icons-movement). Code that uses icons must include one or more of these modules as a dependency. This ensures that each individual icon is only loaded once even if multiple modules need it, but it also causes unused icons to be loaded, since most code doesn't use all icons in a bundle, and many features use only one or two icons each from multiple bundles.
Colors
The icon SVGs are monochrome and use pure black (#000) as their fill color. This is then lightened by applying an opacity rule to the icon in CSS, with opacity values for different situations defined in wikimedia-ui-base that approximate colors from the color palette.
Arbitrary colors can't be applied to icons. Instead, additional CSS rules like that embed modified versions of the SVG files are generated by ResourceLoaderImageModule:
.oo-ui-image-progressive.oo-ui-icon-alert { background-image: url( 'data:image/svg+xml,<svg><g fill="#36c"><path d="..." /></g></svg>'); }
Applying the oo-ui-image-progressive CSS class to the icon span then causes the icon to turn blue. Because of this strategy, only a limited set of icon colors can be used: progressive (blue, #36c), destructive (red, #d33), and warning (yellow, #fc3). Not all of these variants are available for all icons, they're made available on an as-needed basis where it makes sense (there is no progressive trash icon or destructive add icon, for example). Every icon also has an inverted variant (white, #fff) that is used when the icon appears on a colored background (on primary buttons for example). The modified versions of these icons are not stored as separate .svg files, but are generated automatically by reading the .svg file and wrapping it in <g fill="color">...</g>.
Language and right-to-left support
Some icons have both an LTR and an RTL version, stored as separate SVG files (e.g. arrowNext-ltr.svg and arrowNext-rtl.svg), and ResourceLoaderImageModule automatically serves the appropriate one based on the interface language. For icons with language-specific versions (e.g. bold and italic), there is a separate SVG file for each distinct version, but multiple languages can share the same version (for example, Armenian uses bold-armn-to.svg, while bold-cyrl-zhe.svg is used for Kyrgyz, Russian and Ukranian).
Each icon bundle has a JSON file (e.g. icons-movement.json) that defines which icons are in it, which SVG file is used for each version of the icon (LTR, RTL and language versions), and which color variants are available for each icon. Some example icon definitions:
"subscript": { "file": { "ltr": "images/icons/subscript-ltr.svg", "rtl": "images/icons/subscript-rtl.svg" } }, "unLink": { "file": "images/icons/unLink.svg", "variants": [ "destructive" ] }, "underline": { "file": { "default": "images/icons/underline-a.svg", "lang": { "en,de": "images/icons/underline-u.svg" } } },
Extensibility
Extensions can't add icons to the existing OOUI icon bundles, but they can create their own icon module by passing icon definitions in the same format as above to ResourceLoaderImageModule, as follows (simplified example):
{ "ResourceModules": { "ext.foo.icons": { "class": "ResourceLoaderImageModule", "images": { "foo": { "file": "src/icons/foo.svg" }, "bar": { "file": { "ltr": "src/icons/bar-ltr.svg", "rtl": "src/icons/bar-rtl.svg" } } } } }
Once this module is loaded, passing 'foo' or 'bar' as an icon name to an OOUI widget will display the specified icons.
WVUI's current icon system
Delivery and use
Icons are not stored in individual .svg files (, but are all stored together in one big icons.ts file:
export const wvuiIconAdd: Icon = 'M11 9V4H9v5H4v2h5v5h2v-5h5V9z'; export const wvuiIconAlert: Icon = 'M11.53 2.3A1.85 1.85 0 0010 1.21 1.85 1.85 0 008.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z'; // etc.
Note that these icons aren't full SVGs, they're path strings. This means every icon must use a single SVG <path> element. T260815 complains about this restriction and the fact that the SVGs aren't real files, and T276808 talks about the process for converting a multi-path SVG to one suitable for this format.
Callers can use an icon by importing it from this file, then passing it to the Icon component (or to a component that accepts an icon as a prop, and uses the Icon component internally). Because icons are long strings and sometimes complex values (as opposed to short names), they can't be used in the template directly, but have to be passed through the component data in JavaScript. For example:
import { wvuiIconAlert } from '../../themes/icons'; export default Vue.extend( { // ... data() { return { alertIcon: wvuiIconAlert, // ... other component data ... } // ... } );
then alertIcon can be used in the template as follows:
<wvui-icon :icon="alertIcon" />
This results in HTML that looks like this (simplified):
<span class="wvui-icon"> <svg> <path d="..." fill="#000" /> </svg> </span>
Icons are not separated into bundles like they are in OOUI, they're all in one big file. Callers are expected to import only the icons they need, and use tree shaking (which requires a build step) so that only those icons are sent to the client. (This why the usage is a bit more complex than just passing the name of the icon in as a string.) For WVUI consumers that don't use a build step, a separate bundle that exports all icons is available, but it's big (65 KB).
Colors
Because the icon is an <svg> element, its color can be controlled through attributes in the generated SVG, or through CSS. Currently, the Icon component takes an icon-color prop, and that color is used as the fill attribute of the <path> element. T280934 proposes to instead set fill="currentColor", and control the icon color with CSS. Components like buttons would set color: inherit on the icon, so that the icon color matches the color of the adjacent text (e.g. red for quiet destructive buttons, or white for primary buttons).
Language and right-to-left support
Icons can define separate LTR and RTL versions, or language-specific versions, in their definition as follows:
export const wvuiIconImageAdd: IconVariedByDir = { rtl: 'M12 6v2H8v4H2v6a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2zM3.83 17l3.55-4.5 2.52 3 3.55-4.5L18 17zM4 10h2V6h4V4H6V0H4v4H0v2h4z', default: 'M16 17H2l3.5-4.5 2.5 3 3.5-4.5.5.67V8H8V6H2a2 2 0 00-2 2v10a2 2 0 002 2h14a2 2 0 002-2v-6h-5.75z M16 4V0h-2v4h-4v2h4v4h2V6h4V4z' }; export const wvuiIconStrikethrough: IconVariedByLang = { langCodeMap: { en: wvuiIconStrikethroughS, fi: wvuiIconStrikethroughY }, default: wvuiIconStrikethroughA };
A shorthand is available for the common case (63 out of 70) where the RTL version of an icon is the exact mirror image of the LTR version:
export const wvuiIconArrowNext: IconFlipForRtl = { path: 'M8.59 3.42L14.17 9H2v2h12.17l-5.58 5.59L10 18l8-8-8-8z', shouldFlip: true }; export const wvuiIconHelp: IconFlipForRtl = { path: 'M10.06 1C13 1 15 2.89 15 5.53a4.59 4.59 0 01-2.29 4.08c-1.42.92-1.82 1.53-1.82 2.71V13H8.38v-.81a3.84 3.84 0 012-3.84c1.34-.9 1.79-1.53 1.79-2.71a2.1 2.1 0 00-2.08-2.14h-.17a2.3 2.3 0 00-2.38 2.22v.17H5A4.71 4.71 0 019.51 1a5 5 0 01.55 0z M12 17 A2 2 0 0 1 10 19 A2 2 0 0 1 8 17 A2 2 0 0 1 12 17 z', shouldFlip: true, // The help icon (which looks like a question mark) is not flipped in Hebrew and Yiddish, even though those languages are RTL shouldFlipExceptions: [ 'he', 'yi' ] };
The full icon definition object (potentially with multiple versions of the icon) is passed to the Icon component, which decides which one to use based on the lang attribute of the <html> element (or the lang-code prop) and the direction property of the containing HTML element (which can be set in CSS, or with the dir attribute). Icons with shouldFlip: true are rendered as LTR and then flipped in CSS if any of their ancestors has dir="rtl" set, using this rule:
[dir="rtl"] .wvui-icon--flip-for-rtl svg { transform: scaleX( -1 ); }
Extensibility
Extensions can define their own icons in the same format that WVUI uses, and pass them to the Icon component the same way as "core" WVUI icons. The Icon component also accepts an SVG path string directly, e.g. <wvui-icon icon="M11 9V4H9v5H4v2h5v5h2v-5h5V9z" />.