Responsive CSS properties

| permalink | css

In the past, I experimented with responsive properties by creating one variable for each property at every breakpoint.

As expected, the number of variables quickly went out of control without some kind of build system or code pruning.

In order to stay build-free, I went back to the drawing board and revisited the space toggle technique demonstrated by CSS Media Vars.

The Space Toggle Technique

Toggle CSS variables on via the initial keyword; and toggle them off by assigning an empty space to them, thus rendering them inert.

I initially dismissed the idea, because CSS Media Vars implemented the technique by creating per-property responsive variables like I did and ended up becoming too complicated.

This time around, I distilled the technique down to just the toggle variables, and kept only the small, medium and large toggles alongside their negative versions.

I further improved on the technique by replacing the space with a CSS comment marked as important /*!*/, this allowed compatibility with CSS Minifiers since spaces would have been stripped out by them.

/* space toggle based media query */
:root { 
  --on: initial; --off: /*!*/;

  --S: var(--off);
  --M: var(--off);
  --L: var(--off);

  --x-S: var(--on);
  --x-M: var(--on);
  --x-L: var(--on);

@media (width < 640px) {
  :root {
    --S: var(--on); --x-S: var(--off)

@media (640px <= width < 1024px) {
  :root {
    --M: var(--on); --x-M: var(--off)

@media (1024px <= width) {
  :root {
    --L: var(--on); --x-L: var(--off)

/* usage example */
.example {
    var(--S,   1rem 0.5rem)  /* small */
    var(--x-S, 2rem 1rem);   /* not small - medium and large */

  padding-inline: var(--L, 1rem);

Using the media toggles, I no longer need to create individual media queries, I can just use the toggles within a property declaration and stick the value in the fallback.


Forced mixing of values

A limitation with this technique is that all responsive states must be declared together.

.example {
  you cannot split up the responsive states
  because the latter overwrites the former
  margin: var(--S, 1rem);
  margin: var(--x-S, 2rem);

In addition, you cannot omit a responsive state, unless the undeclared state you want is the property's initial value.

.example {
  /* this line */
  margin: var(--S, 1rem) var(--x-S, 0);

  /* is the same as this line */
  margin: var(--S, 1rem);

  /* because the above, when the width is not small, becomes */
  margin: ;

  /* which is invalid, so margin uses the initial value of 0 */
  margin: 0;

This means that per-property responsiveness is most useful when you utilize it to enhance a single baseline design, adding little shims and fixes to prevent the design from breaking.

It becomes counter-productive when you want to implement multiple designs, such as alternate UI widgets, since you cannot group the responsive values separately.

Hard to use beyond two breakpoints

Responsive toggles become harder to use when there are more than two breakpoints.

This is because you will need to start stacking negative toggles or create a lot more toggles to cover all use cases.

.example {
  /* to assign the same value to small and extra large */

  /* I either need to repeat the same value twice */
  margin: var(--S, 1rem) var(--M, 2rem) var(--L, 3rem) var(--XL, 1rem);

  /* or create a toggle for every possible combination of states */
  margin: var(--M, 2rem) var(--L, 3rem) var(--S-XL, 1rem);

  /* or stack the negatives, which is tricky to read */
  margin: var(--M, 2rem) var(--L, 3rem) var(--x-M, var(--x-L, 1rem));

This is not a big issue for me because I'm not a fan of creating a million breakpoints.

I feel a maximum of two breakpoints and three states provides enough granularity for anything that's not a dashboard, and for dashboards it's better to rely on intrinsic sizing instead of or in combination to breakpoints.

In closing

With the limitations outlined, per-property responsiveness is not the final word in responsive design, but simply another option with its own pros and cons.

For myself, I always believed in less is more. So being forced to stick with a single baseline design and a maximum of two breakpoints isn't a problem, since it aligns with my own preferences and needs.