Overview
During design and development, colors should be referenced by name. Using named colors has a few important benefits:
- More meaningful than hex values in mockups and code
- Encourages consistency and accessibility across the application
- Support for alternate themes
Some examples of named colors in the Kolibri Design System include:
tokens.textpalette.blacktokens.errorpalette.red.v_600tokens.primarybrand.primary.v_500brand.primary.v_600#2547F3brand.secondary.v_400#FFD533palette.grey.v_400#999999Care must be taken as we adjust the colors in the application. There are a number of considerations to take into account, including:
- Branding, aesthetics, and color harmony
- Sufficient contrast between foreground and background colors
- Accommodation of color blind users
- Maintainability and consistency of the application and code
- Amenability to theming by using tokens correctly
Usage
Colors can be referenced in two ways:
- CSS variables for every static color, in
<style>blocks, SCSS files, and in some cases, inline:stylebindings. - JavaScript accessors on Vue components, for colors that need to be built at runtime (for example, a color combined with a custom opacity).
Both are set up by Vue.use(KThemePlugin) (see
Installation
) and stay in sync when brand colors change at runtime via
setBrandColors() or setTokenMapping().
setBrandColors() expects every v_N shade of each default brand
color, and logs a console warning naming any that are missing. Read through
$themeBrand each time: a saved sub-object such as
const primary = $themeBrand.primary goes stale after
setBrandColors().
CSS variables
Theme values are emitted as CSS variables on html:root in three layers:
--tokens-*for named color tokens (e.g.--tokens-primary)--brand-*for brand color scales (e.g.--brand-primary-v500)--palette-*for the full palette (e.g.--palette-grey-v200)
Reference them directly with var(--name). For example, to color text using the
error token:
<div class="error-message">This is an error</div>
<style>
.error-message {
color: var(--tokens-error);
}
</style>
This will display:
Use a class rule in a <style> block for new styles. You can also
reference a variable from an inline :style binding when a class rule is not
practical:
<div :style="{ color: 'var(--tokens-error)' }">This is an error</div>
For palette and brand variables, replace the dots in the path with hyphens and drop the
underscore (palette.grey.v_400 becomes --palette-grey-v400). Token
names are used as-is.
Computed styles
Colors are also available on every Vue component as JavaScript objects. Adding
Vue.use(KThemePlugin) makes the following objects available:
$themeBrandcontains colors related to the aesthetic color scheme$themeTokenscontains colors with special meanings in Kolibri$themePalettecontains a wide range of additional compatible colors
Use these when you must build a color value in JavaScript. For a static color, use a CSS
variable instead. For example, to make a translucent background from
$themeTokens.primary, compute the color and bind it with a
computed style:
import Color from 'color';
export default {
computed: {
translucentPrimary() {
return Color(this.$themeTokens.primary).alpha(0.3).string();
},
},
};
<div :style="{ backgroundColor: translucentPrimary }">Translucent primary</div>
This will display:
Darken utilities
You can apply darken utilities $darken1, $darken2, and
$darken3 to palette colors and tokens to achieve their darker shades. They are
available on every Vue component.
These utilities shouldn't be overused. Always check if there is a shade in the palette available that can be used instead.
<div :style="{ backgroundColor: 'var(--palette-red-v600)' }">base</div>
<div :style="{ backgroundColor: $darken1($themePalette.red.v_600) }">$darken1</div>
<div :style="{ backgroundColor: $darken2($themePalette.red.v_600) }">$darken2</div>
<div :style="{ backgroundColor: $darken3($themePalette.red.v_600) }">$darken3</div>
base$darken1$darken2$darken3
For pseudo-classes, use a computed property and bind it with v-bind(). For
example, to darken a button on hover:
computed: {
deleteButtonHoverColor() {
return this.$darken1(this.$themePalette.red.v_600);
},
},
.delete-button {
background-color: var(--palette-red-v600);
}
.delete-button:hover {
background-color: v-bind(deleteButtonHoverColor);
}
Computed classes (deprecated)
$computedClass can be used to dynamically create new classes for
pseudo-elements such as :hover or :focus. Replace it with a rule
in a <style> block.
If the value is a theme color, use its CSS variable. For example, to color a placeholder with the disabled text token:
<!-- Before -->
<input
class="sample-input"
:class="$computedClass({ '::placeholder': { color: $themeTokens.textDisabled } })"
>
<!-- After -->
<input class="sample-input">
<style>
.sample-input::placeholder {
color: var(--tokens-textDisabled);
}
</style>
If the value comes from a prop, return it from a computed property and bind that property
with v-bind(). The computed property must always return a color. For a theme
default, return the CSS variable as a string. For example, where
hoverBackgroundColor is a prop:
// Before
computed: {
styles() {
const hoverBackgroundColor = this.hoverBackgroundColor || this.$themeBrand.primary.v_100;
return {
':hover': { backgroundColor: hoverBackgroundColor },
};
},
},
// After
computed: {
tabHoverBackgroundColor() {
return this.hoverBackgroundColor || 'var(--brand-primary-v100)';
},
},
.tab:hover {
background-color: v-bind(tabHoverBackgroundColor);
}
If the value comes from a darken utility, bind it with v-bind() as in the
darken utilities
example. No CSS variable exists for a darkened color.
If the value is a focus outline, use :focus-visible. The global styles from
common (see
Installation
) already give every element that matches :focus-visible the same outline
style as $coreOutline. If the class sets only
{ ':focus': $coreOutline }, delete it. If the class changes the outline,
override only that property. For example, to remove the outline offset on a button:
<!-- Before -->
<button
class="k-chip-close-button"
:class="$computedClass({ ':focus': { ...$coreOutline, outlineOffset: 0 } })"
></button>
<!-- After -->
<button class="k-chip-close-button"></button>
<style>
.k-chip-close-button:focus-visible {
outline-offset: 0;
}
</style>
If the application imports helper-styles instead of common, write
the full outline:
.k-chip-close-button:focus-visible {
outline: 3px solid var(--tokens-focusOutline);
outline-offset: 0;
}
If the outline goes on an element that does not have focus, keep
$coreOutline in a :style binding, because
:focus-visible never matches there. For example, KCheckbox puts
the outline on its checkbox icon while the focus is on the native input.
KListbox and KSelect outline an option while the container holds
focus through aria-activedescendant.
Note: v-bind() sets its variable on the component's root element.
KOverlay moves its content to #k-overlay, outside that root, so
the moved content doesn't receive the variable. This affects KModal and
KTooltip with appendToOverlay, and any component with
KOverlay as its root. Set the variable with :style on an element
inside the moved content instead:
<KOverlay>
<div :style="{ '--delete-button-hover-color': deleteButtonHoverColor }">
<button class="delete-button">Delete</button>
</div>
</KOverlay>
.delete-button:hover {
background-color: var(--delete-button-hover-color);
}
Note: In Vue 2.7, v-bind() in a <style> block stops updating
if the component's root element is removed and re-added, for example by a v-if.
Wrap the conditional element in a plain, non-conditional root element.
Notation
In the references below we use the following shorthand:
brandrefers to$themeBrandin JavaScript, or--brand-*in CSStokensrefers to$themeTokensin JavaScript, or--tokens-*in CSSpaletterefers to$themePalettein JavaScript, or--palette-*in CSS
Tokens
Color tokens are the most important set of named colors because they have a specific, well-defined usage within the application. Over time, our set of tokens should grow and shrink as necessary to fit the needs of the application.
Color tokens are by themselves abstract and defined by a purpose, not a color value. A theme makes them concrete by mapping them to specific brand colors and palette colors .
When using tokens, it's very important to choose them based on their semantic purpose, not because of their color values. This ensures that new themes can be created effectively.
Brand shortcuts
tokens.primarybrand.primary.v_500Default primary brand color. Commonly used for interactive elements
tokens.primaryDarkbrand.primary.v_600Default dark variant of the primary brand color. Commonly used for the hover state of interactive elements
UI Colors
tokens.successpalette.green.v_600Indicates the successful completion of an action in the application
Text
tokens.textpalette.black
Normal text color. (Typically used on top of the $themeTokens.surface color)
tokens.annotationpalette.grey.v_700
Text color with lower visual weight. (Typically used on top of the
$themeTokens.surface color)
tokens.textDisabledpalette.grey.v_300
Text color with lowest visual weight. (Typically used on top of the
$themeTokens.surface color)
tokens.textInvertedpalette.white
Text color for creating sufficient contrast when used on dark backgrounds (such as
$themeTokens.primary)
Learner activity
User-related labels
Content-related labels
tokens.practicepalette.blue.v_400tokens.watchpalette.lightblue.v_400tokens.listenpalette.pink.v_500tokens.readpalette.red.v_500tokens.explorepalette.orange.v_500tokens.createpalette.green.v_500tokens.reflectpalette.yellow.v_500tokens.topicpalette.grey.v_800Scales
A color scale – sometimes called a color ramp – is an evenly-spaced ramp of shades for a
particular color hue. In the Kolibri Design System, we follow
Google's Material convention
and segment colors into brightness levels, named v_50, v_100,
v_200, v_300, v_400, v_500,
v_600. Note that v_50 is only present on select color families:
palette.green.v_50#E5F9EBpalette.green.v_100#CCF4D8palette.green.v_200#99E9B1palette.green.v_300#66DD8Apalette.green.v_400#33D263palette.green.v_500#00C73Cpalette.green.v_600#00992EDue to the inconsistent way that humans perceive color and light, computing these scales is both art and science. It should not be done by simply sliding a "brightness" setting. We used materialpalettes.com to generate the scales for our primary and secondary brand colors. The same should be done for new themes.
Brand colors
Brand colors are chosen to reflect the mood, identity, or trademark of an application or an organization. The design system defines primary (dominant) and secondary (accent) branded color hues.
brand.primary.v_50#F5F7FFbrand.primary.v_100#D9E1FDbrand.primary.v_200#B4C3FBbrand.primary.v_300#8EA4F9brand.primary.v_400#6986F7brand.primary.v_500#4368F5brand.primary.v_600#2547F3Palette
A color palette is a set of generic base colors that cover a wide range of the color spectrum. We use the 2014 Material Design color palette, which has many colors to choose from.
In our design system, colors from the palette have names like pink,
grey, and amber.
With the exception of grey values, choosing arbitrary colors from the palette is only slightly better than choosing arbitrary hex values from the full color spectrum. No consistency or meaning is ensured, and they should generally be avoided in favor of specific color tokens and brand colors.
Grey values
A scale of standard greys
palette.white#FFFFFFpalette.black#000000palette.grey.v_50#FAFAFApalette.grey.v_100#F5F5F5palette.grey.v_200#E6E6E6palette.grey.v_300#CCCCCCpalette.grey.v_400#999999palette.grey.v_700#666666palette.grey.v_800#333333palette.grey.v_900#000000Full palette
The complete set of colors available in the palette
palette.red.v_50#FFECE9palette.red.v_100#FFD9D3palette.red.v_200#FFB4A7palette.red.v_300#FF8E7Cpalette.red.v_400#FF6950palette.red.v_500#FF4324palette.red.v_600#D21E00palette.pink.v_50#FFF2F3palette.pink.v_100#FFE5E8palette.pink.v_200#FFCCD1palette.pink.v_300#FFB2BBpalette.pink.v_400#FF99A4palette.pink.v_500#FF7F8Dpalette.pink.v_600#F76474palette.blue.v_50#F5F7FFpalette.blue.v_100#D9E1FDpalette.blue.v_200#B4C3FBpalette.blue.v_300#8EA4F9palette.blue.v_400#6986F7palette.blue.v_500#4368F5palette.blue.v_600#2547F3palette.lightblue.v_50#E5F5FDpalette.lightblue.v_100#CCEAFCpalette.lightblue.v_200#99D5FApalette.lightblue.v_300#66C1F7palette.lightblue.v_400#33ACF5palette.lightblue.v_500#0097F2palette.lightblue.v_600#0079C2palette.darkgreen.v_50#E5EEEBpalette.darkgreen.v_100#CCDDD6palette.darkgreen.v_200#99BBADpalette.darkgreen.v_300#669A85palette.darkgreen.v_400#33785Cpalette.darkgreen.v_500#005633palette.darkgreen.v_600#00331Epalette.green.v_50#E5F9EBpalette.green.v_100#CCF4D8palette.green.v_200#99E9B1palette.green.v_300#66DD8Apalette.green.v_400#33D263palette.green.v_500#00C73Cpalette.green.v_600#00992Epalette.orange.v_50#FFF1E5palette.orange.v_100#FFE4CCpalette.orange.v_200#FFC899palette.orange.v_300#FFAD66palette.orange.v_400#FF9133palette.orange.v_500#FF7600palette.orange.v_600#E56A00