Skip to content

Component Tokens

Ali Stump edited this page Sep 6, 2024 · 15 revisions

Component Tokens

The Goal

Component tokens define design pattern decisions at the component level. They meet user requirements for a clear and useful component theming API.

While it is possible to create a variable for each style property we need to balance customization with scale and maintainability. Allowing customer white-labeling that makes Calcite Components feel like a part of their apps while maintaining the underlying Calcite design patterns. To this end, all component tokens should reference/fallback to global tokens.

Naming Pattern

--calcite-[component]-[group]-[property]-[state]

State can be "hover", "pressed", "selected" or nothing.

  • A state that is shared by :hover and :focus should use -hover
  • A state shared by :focus and :active should use -pressed
  • A state which includes :active and/or custom states like [selected] and [checked] should use -pressed
  • An edge case design pattern may require a third variable state for components with unique styling when in a custom attribute state like [selected] and [checked] which is different than -pressed. This should use -selected.
--calcite-button-background-color
--calcite-button-background-color-hover
--calcite-button-background-color-pressed

Making component tokens

Due to the CSS cascade and how CSS variables can penetrate the shadowDOM it is not recommended you create tokens for sub-components or slotted components unless the wrapping component is overriding the component's pattern (i.e., overriding a token value). When a pattern is shared between related components a single token may be used.

flowchart TD
    A1{<a target="_blank" href="https://github.com/Esri/calcite-design-system/blob/6ccb12a17ec371ba13fe144f8d37fdfb607dfce2/packages/calcite-components/stencil.config.ts#L17">Are components associated?</a>}
    A2{Can it be used independently?}
    B1{Look at the component in Figma.<br />Does it share a visual pattern<br />with associated components?}
    C1{It can share tokens}
    C2{It should have unique tokens}

    A1 --> |Yes| A2
    A1 -->|No| C2
    A2 -->|No| C1
    A2 -->|Yes| B1
    B1 -->|Yes| C1
    B1 -->|No| C2
Loading

Example: --calcite-combobox-background-color is used in both Combobox & ComboboxItem. This is documented in the JSDocs at the top of each component's SCSS file.

This is also the case for elements in the component which share a pattern. In most use-cases icons in the components should reference the -text-color token. In rare cases when Icons should not follow the text color pattern, check if it can use the -indicator-color pattern. If not, add an -icon- element to the naming schema.

For Each Component

1. Make sure to branch off dev

Some components in the epic/7180-component-tokens branch are very out of date with the dev branch. This makes sure the most recent JSX, resources and utils are being used.

2. Review Figma design for patterns

  • What components are related to this component?
  • Are there shared interaction patterns?
  • Are the use cases covered by -hover and -pressed?
  • Is there an indicator pattern? This is usually border-input or transparent by default and -brand, -brand-press when :active or :hovered.

3. Review component for "tokenizable" styles

The most common token styles are colors, shadow, and radius. However some components may need to have additional tokens to meet user requirements. While some components already have tokens, many do not. Use the UIKit, your own intuition, as well as feedback from the team to decide how many tokens you need to create to apply styles to the following CSS properties across the component.

  • background-color
  • border-color
  • color (text-color)
  • box-shadow (shadow)
  • border-radius (corner-radius)

4. Update component SCSS to use CSS variable tokens

[property]: var([component-token], var([global-token/fallback]));

Sometimes it's useful to utilize -internal- domain tokens. These are not considered official Calcite Design Tokens and are for developer convenience. If a pattern is observed in -internal- tokens. Bring it up to the team for consideration to be move to a Calcite Design Token. Read more about internal tokens here.

5. Write tests

E2E
  • Use themed from common tests
  • If a token does not apply in the default context, add a new describe block
  • Use html formatter for non-default component tests

Starter template

describe("theme", () => {
  describe("default", () => {
    themed("calcite-component", {});
  });
});

Example

describe("theme", () => {
  describe("default", () => {
    themed("calcite-component", {
      // this is the name of the token
      --calcite-component-background-color: {
        // optional. This defaults to the first component in the test template.
        selector: 'calcite-component',
        // optional. The selector pattern to target a specific element within the shadow dom of the Selector component
        shadowSelector: `.${CSS.container}`
        // the CSS property you have applied a variable to. This must be written in camelCase format.
        targetProp: "backgroundColor"
      }
    });
  });
});
Storybook
  1. Find or create a component file in packages/calcite-components/src/custom-theme/[component];
  2. Use named exports and tagged template literals with html formatting to define the test component templates.
  3. Export an object of component tokens with the token name in camelCase as the key and an empty string as the value.
  4. Make sure the component templates are imported into packages/calcite-components/src/custom-theme.stories.ts and included in the kitchenSink template and the default exported args.
export const actionTokens = {
  calciteActionIndicatorColor: "",
  calciteActionBackgroundColor: "",
  calciteActionBackgroundColorHover: "",
  calciteActionBackgroundColorPressed: "",
  calciteActionTextColor: "",
  calciteActionTextColorPressed: "",
};

export const actionBarTokens = {
  calciteActionBarExpandedMaxWidth: "",
  calciteActionBarItemsSpace: "",
};

export const actionBar = html`<calcite-action-bar layout="horizontal" style="width:100%">
  <calcite-action-group>
    <calcite-action text="Add" icon="plus"> </calcite-action>
    <calcite-action text="Save" icon="save"> </calcite-action>
    <calcite-action text="Layers" icon="layers"> </calcite-action>
  </calcite-action-group>
  <calcite-action-group>
    <calcite-action text="Add" icon="plus"> </calcite-action>
    <calcite-action text="Save" active icon="save"> </calcite-action>
    <calcite-action text="Layers" icon="layers"> </calcite-action>
  </calcite-action-group>
  <calcite-action slot="actions-end" text="hello world" icon="layers"> </calcite-action>
  <!-- The "bottom-actions" slot is deprecated -->
  <calcite-action slot="bottom-actions" text="hello world 2" icon="information"> </calcite-action>
</calcite-action-bar>`;
import {
  actionBar,
  actionTokens,
  actionBarTokens,
} from "./custom-theme/actions";

// ...
const kitchenSink = (args: Record<string, string>, useTestValues = false) =>
  html`<div style="${customTheme(args, useTestValues)}">
    <div class="demo">
      <div>${actionBar}</div>
    </div>
  </div>
</div>`;

export default {
  title: "Theming/Custom Theme",
  args: {
    ...actionTokens,
    ...actionBarTokens,
  },
};

export const theming_TestOnly = (): string => {
  return kitchenSink(
    {
      ...actionTokens,
      ...actionBarTokens,
    },
    true,
  );
};
Demo Page
<demo-theme tokens="--calcite-tokens, --calcite-tokens-as-list">
  <!-- component HTML -->
</demo-theme>

Working Examples

Accordion

Accordion & Accordion Item are directly related and so can be done together. After reviewing the Figma file it was determined that although there were several different instances of text-color and background-color being applied, only a few actual color values were used. This was simplified down to five tokens and applied to both components.

https://github.com/Esri/calcite-design-system/pull/9861

Action

All Action components, Action, Action Bar, Action Group, Action Menu, and Action Pad share many of the same design patterns and therefor should share a lot of the base action tokens. However, several of the more complex action components have their own additional tokens on top of Action.

https://github.com/Esri/calcite-design-system/pull/10058

Icon

Icon is used in all kinds of places. Here most of the work was a quick find-replace of the old --calcite-ui-icon-color token in favor of --calcite-icon-color including in the Calcite Tailwind presets file. This also including removal of some overrides of the --calcite-icon-color token where it was unnecessary and was blocking theming when Storybook tests were run.

https://github.com/Esri/calcite-design-system/pull/10062