-
Notifications
You must be signed in to change notification settings - Fork 195
How to: T shirt sizing in Spectrum CSS
In Spectrum, we have two sizing notions:
-
Scale - This is the overall size of all components on the page, it's either
medium
for desktop, orlarge
for touch. - T-shirt size - This is the size of a specific component, basically a variant/modifier that resizes one instance of a component only. A component whose size is set with t-shirt sizing is still affected by scale.
Traditionally, in CSS, we define modifier classes that change various properties of a given component. If we used this approach for t-shirt sizing, it would look something like this:
.spectrum-Button {
/* ... */
}
.spectrum-Button--sizeM {
height: var(--spectrum-button-primary-m-height);
border-radius: var(--spectrum-button-primary-m-border-radius);
}
.spectrum-Button--sizeL {
height: var(--spectrum-button-primary-l-height);
border-radius: var(--spectrum-button-primary-l-border-radius);
}
As you can see, this would mean that we would have 5 separate modifier classes (XS, S, M, L, XL) that each re-define the same set of properties. Worse, we would have to maintain that set of properties if any of them were ever modified upstream in Spectrum Tokens; i.e. if suddenly Button's font-weight
was adjusted by t-shirt sizing, we would have to manually add 5 font-weight
property declarations or miss out on that change.
Using the magic of CSS custom properties and a few PostCSS plugins, we can ensure that ALL possible modified tokens for t-shirt sizing are automatically reflected in the CSS, with no manual effort outside of defining the modifier classes one time. Here's how.
Spectrum Tokens provides all tokens for all t-shirt sizes of a given component, whether or not they change between sizes. Every variable is consistently named in this form:
--spectrum-COMPONENT-SIZE-VARIANT-PROPERTY-STATE
For example:
--spectrum-button-m-primary-height: var(--spectrum-alias-item-height-m);
--spectrum-button-l-primary-height: var(--spectrum-alias-item-height-l);
Now, instead of directly referencing --spectrum-button-m-primary-height
in the code, we'll actually drop the size and simply reference --spectrum-button-primary-height
, and we do this for all other variables as well:
.spectrum-Button {
height: var(--spectrum-button-primary-height);
}
But wait, not only is --spectrum-button-primary-height
undefined, but it's also meaningless -- it doesn't have a size associated with it. We actually define that variable on the modifier class for the t-shirt size:
/* Medium is the default and does not require an additional class to implement; however we still include one in case it's needed to override scale in a nested use-case (button is a bad example but other components might need it) */
.spectrum-Button,
.spectrum-Button--sizeM {
--spectrum-button-primary-height: var(--spectrum-button-primary-m-height);
}
.spectrum-Button--sizeL {
--spectrum-button-primary-height: var(--spectrum-button-primary-l-height);
}
As you can see, when you apply a t-shirt size class, the only thing that changes is the variables that drive the original class' properties. Instead of re-defining properties, we're re-defining variables! This means there's a single definition of the CSS height
property for .spectrum-Button
, not 5 (one for each t-shirt size).
Of course! Check out the following components and imitate their patterns:
- Field Label - simplest possible example, no regex
- Link - includes a special case where the component is not t-shirt sized by default
- Button - includes per t-shirt size overrides and overall overrides
- Action Button - includes per t-shirt size overrides and overall overrides