We’re used to writing CSS without questioning how far the we can push its boundaries. In general, the answer is that we can push them as far as our creativity and the limits designed let us to.

Creativity depends on how your day is going. Constraints, on the other hand, are written in stone: they’re defined in the specifications and express through formal syntax. This syntax can be quite cryptic, especially for properties that offer a wide range of possibilities.

How to Read the Syntax

Some symbols work just as they do in JavaScript regular expressions, so you’ll probably already be familiar with most of them.

Symbol Meaning Example
<name> Type of data <number>, <angle>
name (without <>) Keyword none
() Literal stuff from the syntax such as the parenthesis of a function oklch()
[] Group [ a b ]
| Alternative <number> | <angle>
? Optional value (0 or 1) [ /alpha ]?
! Required
* 0 or more appearances
+ 1 or more appearances
# Comma-list values <value>#
{n} / {n,m} Repeats. Exactly or as a range <number>{3}
&& Everyone. Any order a && b
|| One or more. Any order a || b
[n,m] Specific numeric range. Min and max values <integer [0,∞]>

With this knowledge, we can now easily read specifications, such as the one for oklch. We can understand why oklch(none none none) is just as valid as oklch(70% 0.1 200) or oklch(70% 0.1 200deg / 50%).

<oklch()> =
  oklch([<percentage>|<number>| none][<percentage>|<number>| none][ <hue>| none][ /[ <alpha-value>| none]]? )

<hue> = <number>|<angle>

<alpha-value> = <number>|<percentage>

We can also understand that the transition property accepts a comma-separated list of transitions.

transition =
  <single-transition>#

We can even see that the orden in which we write the values for the transition shorthand is less strict than we might have thought. For example, both of these are valid: all 200ms linear 50ms normal and 200ms 50ms normal all linear

<single-transition> =
  [ none | <single-transition-property> ]  ||
  <time>                                   ||
  <easing-function>                        ||
  <time>                                   ||
  <transition-behavior-value>

There’s a catch, though. The grammar allows two <time> values, but it doesn’t explicitly tell us which one represents the duration and which one represents the delay. Their position determines their meaning. That’s why reading the specification in full matters, too.

Something similar happens with grid-template-columns. Looking at the syntax alone doesn’t make it immediately obvious that auto-fill and auto-fit can only appear once, and only within an <auto-track-list>.

Conclusion

Understanding the fundamentals of a systema helps us build a solid foundation. That’s why it’s important to read the specifications of the languages we use everyday carefully.

In the aage of AI, it’s reasonable to ask ourselves whether we still need to study programming or read boring documents (and let’s face it, specs can be boring).

If you don’t think so, don’t. , We’ll still be reading, learning, and working with the underlaying technologies and platforms.

The smaller the gap between what you know and what’s actually happening under the hood, the better. When you know nothing (as Jon Snow does), magic fills that gap. But when you know enough to understand almost everything that’s going on, you can make informed decisions and take action based on knowledge you’ve tested and verified.

Resources