Creating Conditional Logic in CSS

Introduction

There are occasions when the ability to utilise conditional statements in CSS would be really useful. CSS lacks if/else type clauses to enable this, but it is possible to arrive at a binary value by combining calc with min and max statements and using the result to 'decide' which rule to apply.

This article presents a use case and one solution to that situation, which can serve as a starting point for future instances you may encounter.

Use Case

Scenario

This requirement cropped up for me recently while I was writing an embed formatter plugin for the Quill rich text editor. After adding support for relative sizing, I realised I needed a means to allow for variable rules for applying responsive sizes. This plugin would be used on any site where the stylistic rules were specific to that site, so it needed to be as versatile as possible.

An image being resized by quill-blot-formatter-2 in Quill rich text editor

Requirement

It wouldn't make sense to introduce blanket rules that establish responsive widths for images and iframes because the editor can make them any size. For example, it is not appropriate to set all images to 100% for viewports smaller than 600px when those pictures may be 10% or 90%. In this scenario, an image with 10% width will increase in size from 60px to 600px.

Because the plug-in is writing the width to the target element, I could have also added a CSS class to the element depending on the width range. This might have been appropriate if the plug-in was just developed for one site, but it confines site designers' options elsewhere to the ranges specified by the plug-in.

The goal is to progressively increase image sizes by multiples of 15%, and set it to 100% if that result is over a defined threshold, for each 20% reduction in screen size from 900 to 500px while guaranteeing an increasing relative sized space alongside block (where text would be for floating images).

Solution

To leave the choice to site designers, I needed a method to allow designers to perform their own calculations. Unfortunately, CSS calc does not support access attribute values directly. For example calc(2 * attr(width)) is not valid syntax. You can perform calculations on css variables however.

I ended up using an inline CSS variable (--resize-width) with an additional helper (data-relative) which can be used in a selector to differentiate between relative and absolute-sized embeds.

Doing this allows the site designer to take an adaptive approach to resizing elements across a range of viewports depending on the --resize-width variable.

Achieving a Conditional Binary Value in CSS

Rattling about in my head are some distant memories of life as a telecoms electronic engineer where logic gates were a common feature. Basically, step down the charge on a contact by a certain threshold, which results either in a zero charge or some small positive amount. Push the result into a positive feedback loop that maxes out at a required level. This gives you either an 'off' or 'on' state. It's also a simplified method to regenerate a square wave from an attenuated signal.

How does this apply to CSS?

We can set a threshold (say 80%) on our variable and use this to test for a zero or positive result with:

max(0%, calc(var(--resize-width) - 80%))

The right hand side of that max calculation will give me a negative if --resize-width is less than 80%, otherwise it will be the remainder (e.g. if 85% is my value, the result is 5%.). Combining that with the full max statement, the result is either zero (for values less than 80%) or some positive value.

For easier explanation, I'll call this result --widthRemainder.

How do we turn this into a binary value?

To mimic the feedback loop, we need to multiply --widthRemainder by some large number that guarantees any positive amount will result in at least 100%. A zero value will result in zero no matter how large the multiplier.

We'll use a calc nested inside a min statement to achieve this:

min(calc(100000 * --widthRemainder), 100%)

Now our value is either 0 or 100% depending on whether the initial value was over 80%. We'll call this value --isOverSize for now.

Using the Binary Value

With my requirement, if the --resize-width value is over 80%, I'll set the width to 100%. If it is not over 80%, I'm going to increase its value by 15% up to a maximum of 75%.

Why set this maximum?

Without this, I run the risk of 'pinching out' the remaining space adjacent to the image. If the image is floating with text wrapping about it, this would result in an unsightly narrow column of text.

Setting a maximum of 75% leaves a minimum of 25% for the text column (set according to your needs here).

This is achieved with a simple min statement wrapping a calc:

min(calc(var(--resize-width) + 15%), 75%)

I'll call this --increased-size here for the final calculation.

Conditionally Setting the Width

We now have an increased size and a binary value that is either 0 or 100% depending on whether the initial --resize-width value was greater than 80%.

To recap, our goal is:

  • If --resize-width is greater than 80%, set the width to 100%
  • For all other sizes, set the width to --resize-width + 15% up to a maximum of 75%

We can achieve this by taking the max value of --increased-size and our binary value --isOverSize:

max(--increased-size, --isOverSize);

Since --isOverSize is 0 when --resize-width is not over 80%, the result will be --increased-size for those cases.

When --resize-width is over 80%, --isOverSize is 100%. In this case, --increased-size is limited to 75% so the result will always be --isOverSize.

To write this as a single line within a media query, with maximum viewport width of 900px, we get:

@media (max-width: 900px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: max(min(calc(var(--resize-width) + 15%), 75%), min(calc(100000 * max(0%, calc(var(--resize-width) - 79.9%))), 100%));
  }
}

Note, by testing for 79.9%, I'm effectively saying 'if --resize-width is greater than or equal to 80% ...'.

Progressively Increasing Widths

With my first step at 900px defined above, I'll set steps at every 20% reduction in screen size down to 368px. With the smaller screen sizes, I want to decrease the maximum size for my --increased-width value as this represents a smaller pixel value.

For my next step down at 720px, my --increased-value will be --resized-width + 30% up to a maximum 70%, while my --isOverSize threshold is for any --resize-width value 60% or greater:

@media (max-width: 720px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: max(min(calc(var(--resize-width) + 30%), 70%), min(calc(100000 * max(0%, calc(var(--resize-width) - 59.9%))), 100%));
  }
}

Continuing this down the various breakpoints, we end up with the following styles:

div.ql-editor [class^="ql-image-align-"] {
  width: var(--resize-width);
}

@media (max-width: 900px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: max(min(calc(var(--resize-width) + 15%), 75%), min(calc(100000 * max(0%, calc(var(--resize-width) - 79.9%))), 100%));
  }
}

@media (max-width: 720px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: max(min(calc(var(--resize-width) + 30%), 70%), min(calc(100000 * max(0%, calc(var(--resize-width) - 59.9%))), 100%));
  }
}

@media (max-width: 576px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: max(min(calc(var(--resize-width) + 45%), 65%), min(calc(100000 * max(0%, calc(var(--resize-width) - 39.9%))), 100%));
  }
}


@media (max-width: 460px) {
  div.ql-editor [class^="ql-image-align-"]k {
    width: max(50%, min(calc(100000 * max(0%, calc(var(--resize-width) - 19.9%))), 100%));
  }
}

@media (max-width: 368px) {
  div.ql-editor [class^="ql-image-align-"] {
    width: 100%;
  }
}

Note that at the last conditional step (at 460px), there's nothing left to test on the left side. It's effectively saying "for anything with --resize-width 20% or more, set to 100%, set everything else to 50%".

The final setting for smallest screen sizes simply sets everything to 100%.

Simulator

Drag the green bar left & right so see how the styles above respond at different widths. You will need a screen size of at least 1050px to see the full range.

Now we have elements that are structurally identical being styled conditionally based on the range that a variable on that element falls into.

Note that the minimum space to the right of the blocks remains more or less static as the size decreases.

Conclusion

In conclusion, this method demonstrates how CSS can be leveraged creatively to simulate conditional logic, even without native support for if/else statements. By combining CSS functions like calc(), min(), and max(), it’s possible to create dynamic styles that adjust based on values like screen width or custom variables. This approach not only provides flexibility in responsive design but also enables designers to handle complex scenarios, such as progressively increasing element sizes, without the need for JavaScript or external logic.

As you apply these techniques in your projects, you’ll likely discover even more use cases where CSS's inherent abilities can be extended. While this solution might not replace traditional conditional logic in all cases, it provides a robust starting point for handling conditional styling within a purely CSS-based workflow, empowering both developers and designers with more control over responsive and adaptable layouts.


  Please feel free to leave any questions or comments below, or send me a message here