Skip to content

How to: T shirt sizing in Spectrum CSS

[ Cassondra ] edited this page Apr 18, 2024 · 3 revisions

What is t-shirt sizing and how does that relate to scales?

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, or large 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.

History: how do we implement modifier classes today?

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.

How do we implement t-shirt sizing?

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).

Examples

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