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.text
palette.black
tokens.error
palette.red.v_600
tokens.primary
brand.primary.v_500
brand.primary.v_600
#2547F3
brand.secondary.v_400
#FFD533
palette.grey.v_400
#999999

Care 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 :style bindings.
  • 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:

This is an error

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:

  • $themeBrand contains colors related to the aesthetic color scheme
  • $themeTokens contains colors with special meanings in Kolibri
  • $themePalette contains 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:

Translucent primary

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:

  • brand refers to $themeBrand in JavaScript, or --brand-* in CSS
  • tokens refers to $themeTokens in JavaScript, or --tokens-* in CSS
  • palette refers to $themePalette in 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.primary
brand.primary.v_500

Default primary brand color. Commonly used for interactive elements

tokens.primaryDark
brand.primary.v_600

Default dark variant of the primary brand color. Commonly used for the hover state of interactive elements

tokens.secondary
brand.secondary.v_500

Default secondary brand color

tokens.secondaryDark
brand.secondary.v_600

Default dark variant of the secondary brand color

UI Colors

tokens.surface
palette.white

Default background color

tokens.appBar
brand.secondary.v_400

Default app bar component color

tokens.appBarDark
palette.black

Default dark color of the app bar component

tokens.link
brand.primary.v_500

Hyperlink text color with the application

tokens.fineLine
palette.grey.v_200

Used for divider lines and rules

tokens.loading
palette.grey.v_800

Color for loaders, spinners, and other progress indicators

tokens.success
palette.green.v_600

Indicates the successful completion of an action in the application

Text

tokens.text
palette.black

Normal text color. (Typically used on top of the $themeTokens.surface color)

tokens.annotation
palette.grey.v_700

Text color with lower visual weight. (Typically used on top of the $themeTokens.surface color)

tokens.textDisabled
palette.grey.v_300

Text color with lowest visual weight. (Typically used on top of the $themeTokens.surface color)

tokens.textInverted
palette.white

Text color for creating sufficient contrast when used on dark backgrounds (such as $themeTokens.primary)

tokens.focusOutline
palette.lightblue.v_400

Used to indicate keyboard focus

tokens.error
palette.red.v_600

Indicates an application or validation error

Learner activity

tokens.progress
palette.lightblue.v_500

Indicates "in-progress" learner activity

tokens.mastered
palette.yellow.v_500

Indicates learner mastery or completion

tokens.correct
palette.green.v_600

Indicates a correct response by a learner

tokens.incorrect
palette.red.v_600

Indicates an incorrect response by a learner

User-related labels

tokens.coachContent
palette.lightblue.v_600

Used for coloring coach content icons

tokens.superAdmin
palette.yellow.v_500

Used for coloring super admin permission key icons

Content-related labels

tokens.practice
palette.blue.v_400
tokens.watch
palette.lightblue.v_400
tokens.listen
palette.pink.v_500
tokens.read
palette.red.v_500
tokens.explore
palette.orange.v_500
tokens.create
palette.green.v_500
tokens.reflect
palette.yellow.v_500
tokens.topic
palette.grey.v_800

Scales

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
#E5F9EB
palette.green.v_100
#CCF4D8
palette.green.v_200
#99E9B1
palette.green.v_300
#66DD8A
palette.green.v_400
#33D263
palette.green.v_500
#00C73C
palette.green.v_600
#00992E

Due 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
#F5F7FF
brand.primary.v_100
#D9E1FD
brand.primary.v_200
#B4C3FB
brand.primary.v_300
#8EA4F9
brand.primary.v_400
#6986F7
brand.primary.v_500
#4368F5
brand.primary.v_600
#2547F3
brand.secondary.v_50
#FFFDF2
brand.secondary.v_100
#FFF5CC
brand.secondary.v_200
#FFEA99
brand.secondary.v_300
#FFE066
brand.secondary.v_400
#FFD533
brand.secondary.v_500
#FFCB00
brand.secondary.v_600
#E5B700

Palette

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
#FFFFFF
palette.black
#000000
palette.grey.v_50
#FAFAFA
palette.grey.v_100
#F5F5F5
palette.grey.v_200
#E6E6E6
palette.grey.v_300
#CCCCCC
palette.grey.v_400
#999999
palette.grey.v_700
#666666
palette.grey.v_800
#333333
palette.grey.v_900
#000000

Full palette

The complete set of colors available in the palette

palette.red.v_50
#FFECE9
palette.red.v_100
#FFD9D3
palette.red.v_200
#FFB4A7
palette.red.v_300
#FF8E7C
palette.red.v_400
#FF6950
palette.red.v_500
#FF4324
palette.red.v_600
#D21E00
palette.pink.v_50
#FFF2F3
palette.pink.v_100
#FFE5E8
palette.pink.v_200
#FFCCD1
palette.pink.v_300
#FFB2BB
palette.pink.v_400
#FF99A4
palette.pink.v_500
#FF7F8D
palette.pink.v_600
#F76474
palette.blue.v_50
#F5F7FF
palette.blue.v_100
#D9E1FD
palette.blue.v_200
#B4C3FB
palette.blue.v_300
#8EA4F9
palette.blue.v_400
#6986F7
palette.blue.v_500
#4368F5
palette.blue.v_600
#2547F3
palette.lightblue.v_50
#E5F5FD
palette.lightblue.v_100
#CCEAFC
palette.lightblue.v_200
#99D5FA
palette.lightblue.v_300
#66C1F7
palette.lightblue.v_400
#33ACF5
palette.lightblue.v_500
#0097F2
palette.lightblue.v_600
#0079C2
palette.darkgreen.v_50
#E5EEEB
palette.darkgreen.v_100
#CCDDD6
palette.darkgreen.v_200
#99BBAD
palette.darkgreen.v_300
#669A85
palette.darkgreen.v_400
#33785C
palette.darkgreen.v_500
#005633
palette.darkgreen.v_600
#00331E
palette.green.v_50
#E5F9EB
palette.green.v_100
#CCF4D8
palette.green.v_200
#99E9B1
palette.green.v_300
#66DD8A
palette.green.v_400
#33D263
palette.green.v_500
#00C73C
palette.green.v_600
#00992E
palette.orange.v_50
#FFF1E5
palette.orange.v_100
#FFE4CC
palette.orange.v_200
#FFC899
palette.orange.v_300
#FFAD66
palette.orange.v_400
#FF9133
palette.orange.v_500
#FF7600
palette.orange.v_600
#E56A00
palette.yellow.v_50
#FFFDF2
palette.yellow.v_100
#FFF5CC
palette.yellow.v_200
#FFEA99
palette.yellow.v_300
#FFE066
palette.yellow.v_400
#FFD533
palette.yellow.v_500
#FFCB00
palette.yellow.v_600
#E5B700