Creating utility classes
Utility classes have been an increasingly popular pattern in the Frontend world within the past few years. Spearheaded by one of the most popular CSS abstractions, Tailwind CSS, its adoption in webpages and web apps around the world continues to grow day by day. Sentro allows you to create utility classes based on the tokens you've defined at the top of your style file (tokens, breakpoints, keys) using a module's *-registry-get()
functions.
Why use this over Tailwind?
There are a lot of benefits to using Tailwind as your main utility class manager. It's easier to set up, conventions are laid out for you so you don't have to think of an API, it's easily configurable, and scales well. For the most part, for those who are looking to using something that's not going to be heavy in terms of the amount of work you want to put in, Tailwind would be your best bet.
However, there are some who want a more specific way of naming utility classes that makes sense in the context of the practices established in the design system. Instead of relying on Tailwind making the styling API decisions for you, with Sentro you are in control. For those who want a more bespoke API for their utility classes, Sentro provides you with the tools you need to achieve your ideal API for utility classes.
What are registries?
Registries in Sentro act as a query-able storage for all the tokens in your design system. They are essential to all operations you do with Sentro as all essential functions and mixins greately depend on it. Sentro allows you to access the registry using the *-registry-get()
functions built into the three main modules (token, breakpoint, keys).
The *-registry-get()
functions
Its usage is quite broad since it only has one specific purpose: to return the registry of a module. Hence you can use it with a number of things from creating custom validators, to what we're going to be doing: creating utility classes.
Creating utility classes with registries
To create utility classes with registries, we first need to define our design tokens.
@use 'pkg:@matteusan/sentro' with ($prefix: 'sdc', $context: 'theme');
@include sentro.breakpoint-config((
'small': 320px,
'medium': 640px,
'large': 890px,
'xlarge': 1077px
));
@include sentro.token-config((
'clr': (
'orange': (
100: hsl(24, 80%, 93%),
200: hsl(24, 80%, 80%),
300: hsl(24, 80%, 70%),
400: hsl(24, 80%, 53%),
600: hsl(24, 80%, 40%),
800: hsl(24, 80%, 20%),
),
'amber': (
100: hsl(36, 85%, 93%),
200: hsl(36, 85%, 80%),
300: hsl(36, 85%, 70%),
400: hsl(36, 85%, 53%),
600: hsl(36, 85%, 40%),
800: hsl(36, 85%, 20%),
),
),
'padding': (
'xs': sentro.to-rem(5px),
...
),
...
));
Now we need to define what kind of utility classes we need. This step is necessary to reduce the number of unnecessary utility classes that might become bloat down the line. To do this, we can use the $targets
and $exceptions
parameters of the *-registry-get()
functions and define what tokens get to be utility classes.
$colors: sentro.token-registry-get($targets: ('clr')); // Since we're using clr- as a prefix for all colors.
$rest: sentro.token-registry-get($exceptions: ('clr')); // Get everything else except clr- tokens.
$breakpoints: sentro.breakpoint-registry-get(); // Get all breakpoint tokens.
To understand what this does, let's take a look at what the function returns.
$colors: (
'clr-orange-100',
...
'clr-orange-800',
'clr-amber-100',
...
'clr-amber-800',
);
$rest: (
'padding-xs',
'padding-sm',
'padding-md',
'padding-lg',
'padding-xl',
'padding-xxl',
...
);
$breakpoints: (
'small',
'medium',
'large',
'xlarge'
);
As you can see, it returns all registered tokens in a list format. This way, we can easily use loops to create utility classes for each token.
All that's left now is to create the utility classes. We're going to be using functions from Sentro's token and breakpoint module to do this.
@each $color in $colors {
.fill-#{$color} {
background: sentro.token-get($color);
}
.ink-#{$color} {
color: sentro.token-get($color);
}
.border-#{$color} {
border-color: var(--sdc-bw, 1px) var(--sdc-bs, solid) sentro.token-get($color);
}
@each $breakpoint in $breakpoints {
.\@#{$breakpoint}\:fill-#{$color} {
@include sentro.breakpoint($breakpoint) {
background: sentro.token-get($color);
}
}
.\@#{$breakpoint}\:ink-#{$color} {
@include sentro.breakpoint($breakpoint) {
color: sentro.token-get($color);
}
}
.\@#{$breakpoint}\:border-#{$color} {
@include sentro.breakpoint($breakpoint) {
border-color: var(--sdc-bw, 1px) var(--sdc-bs, solid) sentro.token-get($color);
}
}
}
}
// And other classes follow...
This will create utility classes for all the colors and breakpoints you have defined in your design system. You can then use these utility classes in your HTML to apply styles to your elements. The built classes would look something like this:
.fill-clr-orange-100 {
background: var(--sdc-theme-clr-orange-100);
}
.ink-clr-orange-100 {
color: var(--sdc-theme-clr-orange-100);
}
.border-clr-orange-100 {
border-color: var(--sdc-bw, 1px) var(--sdc-bs, solid) var(--sdc-theme-clr-orange-100);
}
@media screen and (min-width: 320px) {
.@small\:fill-clr-orange-100 {
background: var(--sdc-theme-clr-orange-100);
}
.@small\:ink-clr-orange-100 {
color: var(--sdc-theme-clr-orange-100);
}
.@small\:border-clr-orange-100 {
border-color: var(--sdc-bw, 1px) var(--sdc-bs, solid) var(--sdc-theme-clr-orange-100);
}
}
...
As you can see, the utility classes are created with a bespoke API that is more in line with the design system's practices.