

Writing Logic in CSS
source link: https://iamschulz.com/writing-logic-in-css/?ref=sidebar
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

CSS is a highly specialized programming language focusing on style systems. Because of this unique use case and its declarative nature, it’s sometimes hard to understand. Some folks even deny it’s a programming language altogether. Let’s prove them wrong by programming a smart, flexible style system.
Control Structures
More traditional, general-purpose languages (like JavaScript) give us tools like Conditions (if/then
), Loops (for
, while
), Logical Gates (===
, &&
, etc.) and Variables. Those structures are named differently in CSS, their syntax is wildly different to better accommodate the specific use case of styling a document, and some of those simply weren’t available in CSS up until a few years ago.
Variables
Variables are the most straightforward ones. They’re called Custom Properties in CSS (although everyone calls them variables anyway, even their own syntax).
:root {
--color: red;
}
span {
color: var(--color, blue);
}
The double-dash declares a variable and assigns a value. This has to happen in a scope because doing so outside of a selector would break the CSS syntax. Notice the :root
selector, which works as a global scope.
Conditions
Conditions can be written in a number of ways, depending on where you want to use them. Selectors are scoped to their elements, media queries are scoped globally, and need their own selectors.
Attribute Selectors:
[data-attr="true"] {
/* if */
}
[data-attr="false"] {
/* elseif */
}
:not([data-attr]) {
/* else */
}
Pseudo Classes:
:checked {
/* if */
}
:not(:checked) {
/* else */
}
Media Queries:
:root {
color: red; /* else */
}
@media (min-width > 600px) {
:root {
color: blue; /* if */
}
}
Loops
Counters are both the most straightforward form of loops in CSS, but also the one with the narrowest use case. You can only use counters in the content
property, displaying it as text. You can tweak its increment, its starting point, and its value at any given point, but the output is always limited to text.
main {
counter-reset: section;
}
section {
counter-increment: section;
counter-reset: section;
}
section > h2::before {
content: "Headline " counter(section) ": ";
}
But what if you wanted to use a loop to define a recurring layout pattern? This kind of a loop is a bit more obscure: It’s the Grid’s auto-fill
property.
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
This fills the grid with as many elements as it can fit, while scaling them to fill the available space, but breaking them into multiple rows when it needs. It repeats for as long as it finds items and caps them to a minimum width of 300px and a maximum width of one fraction of its own size. It’s probably easier to see than to explain:
(more info)
And finally, there are looped selectors. They take an argument, which can be a formula to select very precisely.
section:nth-child(2n) {
/* selects every even element */
}
section:nth-child(4n + 2) {
/* selects every fourth, starting from the 2nd */
}
For really special edge cases, you can combine :nth-child()
with :not()
, like:
section:nth-child(3n):not(:nth-child(6)) {
/* selects every 3rd element, but not the 6th */
}
(more info)
You can replace :nth-child()
with :nth-of-type()
and :nth-last-of-type()
to change the scope of those last few examples.
Logic Gates
Ana Tudor wrote an article on CSS Logic Gates. Those work on the idea of combining variables with calc
. She then goes on 3D modeling and animating objects with that. It kinda reads like arcane magic, gets way more insane as the article goes on, and is generally one of the best explanations why CSS in fact is a programming language.
Techniques
The Owl Selector
* + * {
margin-top: 1rem;
}
The Owl Selector selects every item that follows an item. Applying a margin-top
to that effectively adds a gap between the items, like grid-gap
does, but without the grid system. That also means it’s more customizable. You can overwrite your margin-top
and adapt for any kind of content. Want to have 1rem
of space between each item, but 3rem
before a headline? That’s easier to do with an owl selector than in a grid.
Kevin Pennekamp has an in-depth article on it that even explains its algorithm in pseudo code.
Conditional Styling
We can create toggles in our css code that switch certain rules on and off with variables and calc
. This gives us very versatile conditions.
.box {
padding: 1rem 1rem 1rem calc(1rem + var(--s) * 4rem);
color: hsl(0, calc(var(--s, 0) * 100%), 80%);
background-color: hsl(0, calc(var(--s, 0) * 100%), 15%);
border: calc(var(--s, 0) * 1px) solid hsl(0, calc(var(--s, 0) * 100%), 80%);
}
.icon {
opacity: calc(var(--s) * 100%);
transform: scale(calc(var(--s) * 100%));
}
(more info)
Depending on the value of --s
, .box
it will either enable or disable its alert styles.
Automatic contrast colors
Let’s take the same logic one step further and create a color variable that’s dependent on its contrast to the background color:
:root {
--theme-hue: 210deg;
--theme-sat: 30%;
--theme-lit: 20%;
--theme-font-threshold: 51%;
--background-color: hsl(
var(--theme-hue),
var(--theme-sat),
var(--theme-lit)
);
--font-color: hsl(
var(--theme-hue),
var(--theme-sat),
clamp(
10%,
calc(100% - (var(--theme-lit) - var(theme-font-threshold)) * 1000),
95%
)
);
}
This snippet calculates a background color from HSL values and a black or white font color, by inverting the background’s lightness value. This alone could result in low color contrast (a 40% grey font on a 60% grey background is pretty much illegible), so I’ll subtract a threshold value (the point where the color switches from white to black), multiply it by an insanely high value like 1000 and clamp it between 10% and 95%, to get a valid lightness percantage in the end. It’s all controllable by editing the four variables at the beginning of the snippet.
(more info)
This method can also be used to write intricate color logic and automatic themes, based on HSL values alone.
Cleaning up the Stylesheet
Let’s combine what we have so far to clean up the stylesheet. Sorting everything by viewports seems a bit spaghetti-like, but sorting it by component doesnt feel any better. With variables we can have the best of both worlds:
/* define variales */
:root {
--paragraph-width: 90ch;
--sidebar-width: 30ch;
--layout-s: "header header" "sidebar sidebar" "main main" "footer footer";
--layout-l: "header header" "main sidebar" "footer footer";
--template-s: auto auto minmax(100%, 1fr) auto / minmax(
70%,
var(--paragraph-width)
)
minmax(30%, var(--sidebar-width));
--template-l: auto minmax(100%, 1fr) auto / minmax(
70%,
var(--paragraph-width)
)
minmax(30%, var(--sidebar-width));
--layout: var(--layout-s);
--template: var(--template-s);
--gap-width: 1rem;
}
/* manipulate variables by viewport */
@media (min-width: 48rem) {
:root {
--layout: var(--layout-l);
--template: var(--template-l);
}
}
/* bind to DOM */
body {
display: grid;
grid-template: var(--template);
grid-template-areas: var(--layout);
grid-gap: var(--gap-width);
justify-content: center;
min-height: 100vh;
max-width: calc(
var(--paragraph-width) + var(--sidebar-width) + var(--gap-width)
);
padding: 0 var(--gap-width);
}
(more info)
All the global variables are defined at the very top and sorted by viewport. That section effectively becomes the Definition of Behavior, clearing questions like:
- Which global aspects of the stylesheet do we have? I’m thinking of things like
font-size
, colors, repeating measures, etc. - Which frequently changing aspects do we have? Container widths, Grid layouts and the like come to mind.
- How should values change between viewports? Which global styles do apply to which viewport?
Below are the rule definitions, sorted by component. Media Queries aren’t needed here anymore, because those are already defined at the top and put into variables. We can just code along in out stylesheets uninterrupted at this point.
Reading the hash parameter
A special case of pseudo classes is the :target
selector, which can read the hash fragment of the URL. Here’s a demo that uses this mechanic to simulate an SPA-like experience:
(more info)
I’ve written a post on that. Just be aware that this has some serious accessibility implications and needs some JavaScript mechanics to actually be barrier free. Don’t do this in a live environment.
Setting Variables in JavaScript
Manipulating CSS Variables has become a very powerful tool by now. We can also leverage that in JavaScript:
// set --s on :root
document.documentElement.style.setProperty('--s', e.target.value);
// set --s scoped to #myID
const el = document.querySelector('#myID');
el.style.setProperty('--s', e.target.value);
// read variables from an alement
const switch = getComputedStyle(el).getPropertyValue('--s');
The codepen examples above work just like that.
Wrapping up
CSS is very much capable of difining smart and reactive layout systems. It’s control structures and algorithms may be a bit weird compared to other languages, but they’re there and they’re up to the task. Let’s stop just describing some styles and start making them work.
Recommend
-
36
CJSS A CSS based web framework Also, don’t use this project, especially not for anything serious. This is a joke and thught experiment. I won’t help you work through bugs. To install CJSS, add...
-
11
Writing CSS with Accessibility in MindWriting CSS with Accessibility in MindAn introduction to web accessibility. Tips on how to improve the accessibility of your web sites and apps with CSS.This article...
-
6
By TEN BITCOMB, 03.02.2019 My Approach To Writing Maintainable CSS It's been over a year since I wrote my
-
11
Writing Poker Game Logic Live With F# And Active Patterns | FULL STREAM (With Timestamps)415 views•Apr 16, 2021 ...
-
7
CSS Command Problem - What is the Real Logic? advertisements I'm reading and experimenting about CSS lately, however, I'm having this same iss...
-
4
Sep 16th, 2020Writing Good CSS👇 Download Show✏️ Edit Show NotesIn this...
-
3
CSS already has had conditionals in the form of @media queries or @support queries to selectively apply styling to the document. But there is a new proposal called when/else which takes it to a different level.At the time of writi...
-
3
Modern Techniques for Writing Better CSS Dec 08, 2021 15 min read CSS has come a long way since the early days of web development, when tables and various other h...
-
8
Writing CSS-in-JS with Emotion Full Stack Radio
-
3
Writing and testing business logic in F# 2022-10-23 oskar dudyczArchitecture
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK