The Power of CSS Shorthands in the Era of Custom Properties

By combining CSS shorthand properties (or simply “shorthands”) with custom properties (variables), we can encapsulate related system tokens that are meant to travel together in our stylesheets into a single, highly portable token.

We will refer to those as Shorthand Custom Properties.

In this post, we will explore how we can use Shorthand Custom Properties to manage tokens that represent related design decisions, and how to safely expose specific parts of those decisions for consumers to customize.

Encapsulating System Tokens

Taking advantage of CSS shorthands, we can pack tokens that represent related design decisions into a single custom property that maps to native shorthands like background, box-shadow, font, and so on.

Take typography, for example. When defining a headline token, we often need to define the font weight, size, line height, and font family simultaneously.

html {
  --sys-typescale-headline-large-weight: 400;
  --sys-typescale-headline-large-size: 2rem;
  --sys-typescale-headline-large-line-height: 2.5rem;
  --sys-typescale-headline-large-font-family: "Open Sans", sans-serif;
}

We can abstract this to a single custom property mapping to the font shorthand:

html {
  --sys-typescale-headline-large: 400 2rem/2.5rem "Open Sans", sans-serif;
}

This approach is also perfect for representing multi-layer gradients or complex shadows:

html {
  --hero-gradient:
    radial-gradient(at 48% 40% in oklab, white 0px, transparent 50%),
    radial-gradient(at 56% 96% in oklab, pink 0px, transparent 50%);
  --card-shadow: 0px 2px 6px 2px oklch(0 0 0 / 15%), 0px 1px 2px oklch(0 0 0 / 30%);
  /* ... */
}

This reduces implementation errors and gives consumers a clean, framework-agnostic surface area. They simply apply the custom property where they need it.

In the next section, we will see how we can offer more flexibility to the consumers of these Shorthand Custom Properties.

Adding Flexibility

To give consumers the flexibility to adapt these tokens to their brand, we can introduce a dedicated API of smaller custom properties that plug into our main shorthands.

For example, in the case of a gradient, we might expose specific custom properties that represent the source colors of the gradient compositions. For a shadow, we might provide a shadow color custom property to update the color of all layers of the shadow. For typography, we might expose the font family to give consumers the ability to update the font family across the entire typescale system.

The goal is to give consumers opinionated system tokens so they don’t have to start from scratch. At the same time, we need to offer enough flexibility for consumers to adapt these tokens to specific brand needs.

@layer design-system.tokens {
  html {
    --color-1: pink;
    --color-2: blue;
    --hero-gradient:
      radial-gradient(at 48% 40% in oklab, white 0px, transparent 50%),
      radial-gradient(
        farthest-corner circle at 56% 96% in oklab,
        oklch(from var(--color-1) 80% 0.16 h) 0px,
        transparent 50%
      ),
      radial-gradient(
        farthest-corner circle at 0% 96% in oklab,
        oklch(from var(--color-2) 80% 0.16 h) 0px,
        transparent 50%
      );
  }
}

Now, in the gradient case, consumers only need to update --color-1 and --color-2.

Note that because of how CSS shorthands evaluate their internal custom properties, resolution happens at the element level where the shorthand custom property was declared (in our case, the html element). Because of this, consumers should apply their updates at the root html level for global system tokens to compute properly everywhere. We can also use CSS Layers (@layer) to manage the cascade, like the example above. This ensures consumers’ custom properties win over the default system tokens simply by wrapping the system tokens in a layer with lower precedence.

html {
  --color-1: purple;
  --color-2: orange;
}

As you can see, this gives consumers total control over the source colors that compose the gradient, while the component author maintains control over the complex gradient logic.

However, this introduces a new challenge. Since we don’t have full control over the values consumers might pass, we need to find a way to ensure that the shorthand is computed properly even if they provide invalid values.

The Challenge of Invalid Declarations

Shorthands are notoriously strict. If consumers provide an invalid declaration for any constituent part of a shorthand, the entire shorthand breaks, becoming Invalid at Computed-Value Time (IACVT).

For example, if consumers make a typo and set --color-1: 1rem;, the entire --hero-gradient declaration becomes invalid and disappears.

We need to find a way to make our Shorthand Custom Properties more resilient to invalid declarations.

The Solution: @property

Registering our custom properties using the @property rule acts as a firewall. It will validate the exposed longhand custom property before it reaches the shorthand. This ensures that if consumers pass an invalid value, the property will fall back to its initial value defined by the author in the registration.

@property --color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: pink; /* Safe fallback */
}

@property --color-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: blue; /* Safe fallback */
}

Now, if consumers write --color-1: 1rem;, the browser rejects it because 1rem is not a valid value for the data type <color>. The custom property safely falls back to its initial value, pink. As a result, our custom property --hero-gradient remains valid. The layout doesn’t break, and consumers get a visually acceptable result instead of a broken UI.

Handling Complex Types

Validating colors is straightforward through the data-type <color>. But what happens when we try to validate the font-family property in the font shorthand?

html {
  --sys-typescale-headline-large: 400 2rem/2.5rem var(--ref-typeface-base);
}

If we try to register the property that represents the font family, in our example, it’s --ref-typeface-base, we face a challenge. The native font-family parser allows a massive mix of quoted strings ("Helvetica"), custom identifiers (Helvetica), and unquoted generic keywords (sans-serif).

The @property syntax definition is not expressive enough to validate a comma-separated list that mixes random strings and specific unquoted keywords.

To solve this, we need to split the font family name from the generic fallback into two distinct custom properties. We also enforce that the base family uses quoted strings (a standard CSS best practice).

First, we define our base typeface. Notice that we use the <string># syntax instead of just <string>. The # symbol tells the browser to expect a comma-separated list of strings, allowing consumers to pass fallback fonts if they wish (e.g., "Open Sans", "Arial").

@property --ref-typeface-base {
  syntax: "<string>#";
  inherits: false;
  initial-value: "Open Sans";
}

Next, we define the generic fallback, restricting it entirely to valid CSS generic font keywords using the | (OR) combinator:

@property --ref-typeface-generic {
  syntax: "serif | sans-serif | system-ui | monospace | cursive | fantasy | math";
  inherits: false;
  initial-value: sans-serif;
}

With our strict boundaries established, we can construct the typography shorthand safely:

@layer design-system.tokens {
  html {
    --sys-typescale-headline-large:
      400 2rem/2.5rem var(--ref-typeface-base), var(--ref-typeface-generic);
  }
}

Now consumers can easily update the font family for the entire typescale system:

html {
  --ref-typeface-base: "Playfair Display", "Times New Roman", "Georgia";
  --ref-typeface-generic: serif; /* we can also manage the generic fallback from this custom property */
}

Or even more simply:

html {
  --ref-typeface-base: "Roboto";
}

If consumers attempt to update --ref-typeface-base with an invalid token, the custom property will gracefully revert to its initial value (in our case, "Open Sans"). From there, the browser’s default fallback mechanism will take over; if Open Sans is unavailable, it will fall back to the generic fallback, ensuring the overarching font shorthand remains fully intact.

Conclusion

Shorthand Custom Properties provide a native, powerful, and highly portable way to encapsulate related system tokens. They allow you to seamlessly group related design decisions, relying entirely on standard web platform features.

More importantly, when paired with the @property rule, they transform into an incredibly robust API. You get the structural benefits of encapsulation while guaranteeing that a simple consumer typo won’t break your underlying UI.

To see this architectural pattern in action, check out the interactive CodePen demos for gradients and typography, and try passing invalid values into the properties yourself.

The next time you find yourself defining a group of related tokens that must travel together, think of Shorthand Custom Properties. They offer the perfect balance of encapsulation, portability, and resilience for your styles.