DEV Community

Cover image for Semantic Tokens in Chakra UI ⚛
paripsky
paripsky

Posted on

Semantic Tokens in Chakra UI ⚛

Chakra UI v1.8.0 introduced a new feature called Semantic Tokens.
Semantic tokens allow us to use tokens with a specific name in Chakra props that map to a regular design token, for example, we can create a color semantic token that's called "success" that will map to the "green.500" color.

const theme = extendTheme({
  semanticTokens: {
    colors: {
      success: "green.500"
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

and now the "success" token can be used in Chakra components as an "alias" to "green.500"

<Button bg="success">Accept</Button>
Enter fullscreen mode Exit fullscreen mode

which will give us the same result as

<Button bg="green.500">Accept</Button>
Enter fullscreen mode Exit fullscreen mode

Semantic Values

Another cool feature is mapping to a different value based on the current theme, for example, we can map the semantic token "error" to "red.600" in the light theme, and to "red.300" in the dark theme.

const theme = extendTheme({
  semanticTokens: {
    colors: {
      error: {
        default: "red.600",
        _dark: "red.300"
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

This is really useful and allows us to reduce the usage of useColorModeValue, as now instead of

<Button bg={useColorModeValue('red.600', 'red.300')}>Retry</Button>
Enter fullscreen mode Exit fullscreen mode

We can simply use the "error" semantic token:

<Button bg="error">Retry</Button>
Enter fullscreen mode Exit fullscreen mode

Possible Semantic Token fields

As well as creating semantic tokens for colors, we can also create semantic tokens for every scale in Chakra UI: font sizes, borders, radii, sizes and more. see the complete list here and here.

In the following example we'll create semantic tokens for shadows & radii:

const theme = extendTheme({
  semanticTokens: {
    shadows: {
      card: {
        default: "md",
        _dark: "none"
      }
    },
    radii: {
      card: "lg"
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

and now we can use our new tokens like so

<Box boxShadow="card" borderRadius="card">Card Content</Box>
Enter fullscreen mode Exit fullscreen mode

Here's a codesandbox demo by Lazar Nikolov (Chakra UI core team member) demontrating these Semantic Tokens:

Taking things to the next level

Using everything we learned above we can now create a Semantic Tokens based design system with similar variants (50-900) to what chakra has for it's default colors.
We'll start with creating an array for the available color tints (50-900)

const availableColorTints = [
  '50',
  '100',
  '200',
  '300',
  '400',
  '500',
  '600',
  '700',
  '800',
  '900',
];
Enter fullscreen mode Exit fullscreen mode

Now let's create a map of semantic token -> chakra theme color

const tokenToColorMap = {
  primary: 'blue',
  accent: 'teal',
  success: 'green',
  warning: 'orange',
  error: 'red',
  neutral: 'gray',
};
Enter fullscreen mode Exit fullscreen mode

Now we'll build our theme's colors object using the tints array and the token map & we'll includes a simple color inversion logic for the dark & light themes to help us avoid calling useColorModeValue.
(primary.300 becomes blue.300 for the dark theme and blue.600 for the light theme which gives better results than leaving it to be the same color for both in my experience):

const colors = Object.entries(tokenToColorMap).reduce((acc, [token, color]) => {
  availableColorTints.forEach((tint, index) => {
    acc[`${token}.${tint}`] = {
      default: `${color}.${availableColorTints[availableColorTints.length - 1 - index]}`,
      _dark: `${color}.${tint}`,
    };
  });
  return acc;
}, {});
Enter fullscreen mode Exit fullscreen mode

This will generate the following colors object in runtime:

{
  primary.50: {
    default: "blue.900",
    _dark: "blue.50"
  },
  primary.100: {
    default: "blue.800",
    _dark: "blue.100"
  },
  primary.200: {
    default: "blue.700",
    _dark: "blue.200"
  },
  primary.300: {
    default: "blue.600",
    _dark: "blue.300"
  },
  primary.400: {
    default: "blue.500",
    _dark: "blue.400"
  },
  primary.500: {
    default: "blue.400",
    _dark: "blue.500"
  },
  primary.600: {
    default: "blue.300",
    _dark: "blue.600"
  },
  primary.700: {
    default: "blue.200",
    _dark: "blue.700"
  },
  primary.800: {
    default: "blue.100",
    _dark: "blue.800"
  },
  primary.900: {
    default: "blue.50",
    _dark: "blue.900"
  },
  accent.50: {
    default: "teal.900",
    _dark: "teal.50"
  },
  // etc.
}
Enter fullscreen mode Exit fullscreen mode

Now we can use our cool new Semantic Tokens in our components

<Button bg="primary.300" color="neutral.300">Ok</Button>
<Button bg="accent.400" color="neutral.400">Cancel</Button>
<Box bg="error.300" borderColor="error.400">Something went wrong</Box>
Enter fullscreen mode Exit fullscreen mode

A full example of a theme file & usage is available in my personal website's github page.

Summary

We discussed what are semantic tokens, what are their benefits and how we can use them to our advantage when building a design system that's based on Chakra UI's built-in tokens.

Thanks for reading! 🚀


To read more about Semantic Tokens, see Chakra UI's docs.

Discussion (0)