Skip to content

Behavioural Tokens

Why?

Group default, :hover and :active state tokens meaningfully.

The behavioural token encodes through data structure (rather than naming convention) the relationship between tokens used in default, hover and active states.

Hidden in design tokens

Many design systems will have, somewhere in the semantic layer, tokens that look something like this:

Semantic Tokens
Name
Light
Dark
Semantics
Primary
Background
Default
Primitives/Primary/300
Primitives/Primary/1100
Hover
Primitives/Primary/400
Primitives/Primary/1000
Active
Primitives/Primary/500
Primitives/Primary/900
Semantics
Secondary
Background
Default
Primitives/Secondary/300
Primitives/Secondary/1100
Hover
Primitives/Secondary/400
Primitives/Secondary/1000
Active
Primitives/Secondary/500
Primitives/Secondary/900

There's a clear intent here, expressed through the naming, that a component using a background color of Semantics/Primary/Background/Default should also use a background color of Semantics/Primary/Background/Hover when in hover state (if applicable) and likewise for active state.

However, there's no easy way to apply this to a component that doesn't require the designer to manually select each one.

A development hotch-potch

Similarly, in development, at the worst case you will end up with something like this:

Positive
Negative
css
.widget-positive {
  background-color: var(--sentiment-positive-background-default);
  color: var(--sentiment-positive-foreground-default);
  border-color: var(--sentiment-positive-border-default);

  &:hover {
    background-color: var(--sentiment-positive-background-hover);
    color: var(--sentiment-positive-foreground-hover);
    border-color: var(--sentiment-positive-border-hover);
  }

  &:active {
    background-color: var(--sentiment-positive-background-active);
    color: var(--sentiment-positive-foreground-active);
    border-color: var(--sentiment-positive-border-active);
  }
}

.widget-negative {
  background-color: var(--sentiment-negative-background-default);
  color: var(--sentiment-negative-foreground-default);
  border-color: var(--sentiment-negative-border-default);

  &:hover {
    background-color: var(--sentiment-negative-background-hover);
    color: var(--sentiment-negative-foreground-hover);
    border-color: var(--sentiment-negative-border-hover);
  }

  &:active {
    background-color: var(--sentiment-negative-background-active);
    color: var(--sentiment-negative-foreground-active);
    border-color: var(--sentiment-negative-border-active);
  }
}
html
<div class="widget-positive">Positive</div>
<div class="widget-negative">Negative</div>

This is just for one component with two sentiments - when other components need sentimentality, there's nothing here that can be reused.

Torquens

The behavioural token exists to fill this void. Design systems implementations may already have something in place for this:

Positive
Negative
scss
@use "behavioural" as *;

.widget-positive {
  @include behavioural(background-color, --sentiment-positive-background);
  @include behavioural(color, --sentiment-positive-foreground);
  @include behavioural(border-color, --sentiment-positive-border);
}

.widget-negative {
  @include behavioural(background-color, --sentiment-negative-background);
  @include behavioural(color, --sentiment-negative-foreground);
  @include behavioural(border-color, --sentiment-negative-border);
}
scss
@mixin behavioural($prop, $color) {
  #{$prop}: var(#{$color}-default);

  &:hover {
    #{$prop}: var(#{$color}-hover);
  }

  &:active {
    #{$prop}: var(#{$color}-active);
  }
}
html
<div class="widget-positive">Positive</div>
<div class="widget-negative">Negative</div>

But the pain here is that this is using a token that doesn't exist in your semantics.

The solution here is to create a new set of tokens that can switch between the different states.

Behavioural Tokens
Name
Default
Hover
Active
Semantics
Primary
Background
Semantics/Primary/Background/Default
Semantics/Primary/Background/Hover
Semantics/Primary/Background/Active
Foreground
Semantics/Primary/Foreground/Default
Semantics/Primary/Foreground/Hover
Semantics/Primary/Foreground/Active
Border
Semantics/Primary/Border/Default
Semantics/Primary/Border/Hover
Semantics/Primary/Border/Active
Semantics
Secondary
Background
Semantics/Secondary/Background/Default
Semantics/Secondary/Background/Hover
Semantics/Secondary/Background/Active
Foreground
Semantics/Secondary/Foreground/Default
Semantics/Secondary/Foreground/Hover
Semantics/Secondary/Foreground/Active
Border
Semantics/Secondary/Border/Default
Semantics/Secondary/Border/Hover
Semantics/Secondary/Border/Active

Now, consumers can reference Semantics/Primary/Background, and have a mode to switch between states.