DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 03: CSS

I had the first two languages planned before I started the series. For the third I decided to ask GitHub Copilot. Its suggestions were:

  • write a lot more episodes about Python
  • go alphabetically from C to Rust, then continue about Rust for the rest of the series
  • actually write a lot of languages but with a lot of repetition
  • HTML

OK, so maybe AI won't be replacing us anytime soon. But that last suggestion wasn't too crazy - HTML may not be a programming language, but CSS basically turned into one!

This episode is not about centering elements, or any such things, we'll be writing real programs in CSS!

Hello, World!

First, the Hello World!

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <style>
    :root {
      --who: "World";
    }
    body {
      margin: 0;
      min-height: 100vh;
      font-size: 48px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    body::after {
      content: "Hello, " var(--who);
    }
  </style>
</head>
<body>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Which looks just as you expected, without any content in the HTML body:

Hello

Ignore rules on the body, they're just centering. The interesting techniques are elsewhere.

We can set variables in CSS with --variablename: value;. It is then inherited by every child element. We can use such variables with var(--variablename).

And we can create "pseudo-elements" like ::after and ::before, and set their content. These were added to CSS to deal with things like list numbering. When you say <ol><li>One</li><li>Two</li></ol>, HTML actually needs to display 1. One 2. Two. Where those "1." and "2." come from? From just such pseudo-elements (in this case ::marker, but close enough).

Most "programming with CSS" will rely heavily on pseudoelements. Oh and they're also confusingly named - body::after means "inside body; after all contents" not "after body".

FizzBuzz

Let's write the real program now, the FizzBuzz! For this we'll put 100 empty spans in the HTML, and do the FizzBuzz with pure CSS:

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      margin: 0;
      min-height: 100vh;
      counter-reset: fizzbuzz-counter;
    }
    span {
      counter-increment: fizzbuzz-counter;
    }
    span::after {
      content: ", ";
    }
    span:last-child::after {
      content: ".";
    }
    span::before {
      content: counter(fizzbuzz-counter);
    }
    span:nth-child(3n)::before {
      content: "Fizz";
    }
    span:nth-child(5n)::before {
      content: "Buzz";
    }
    span:nth-child(15n)::before {
      content: "FizzBuzz";
    }
  </style>
</head>
<body>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
  <span></span><span></span><span></span><span></span><span></span>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And what's that? A real nicely formatted FizzBuzz:

FizzBuzz

How does it work? We use a few new techniques here.

Every span has two pseudoelements, ::before and ::after.

The ::before pseudoelement gets the counter value or "Fizz" or "Buzz" or "FizzBuzz". To support such important features as light and dark stripes on tables, CSS lets us apply rule to elements every N elements. Rules of same specificity written later take precedence. So span:nth-child(5n)::before will only apply to every 5th element, except those for which span:nth-child(15n)::before takes precedence.

We don't use CSS variables for this, we use CSS counters. Counters are created with counter-reset: countername;, incremented with counter-increment: countername;, and then accessed with counter(countername).

The ::after pseudoelement is just either comma for all other elements, or period for the final element which we select with :last-child. This feature is actually occasionally used in real life, to represent lists as sentences.

Counters are also more useful than you'd think - you don't need them for lists, but for things like header numbering with section and subsection numbers, CSS can do that quite easily with counters.

Fibonacci Numbers

And now we run into a very unexpected problem:

  • CSS has strings and numbers AND NO WAY TO CONVERT ONE TO THE OTHER!
  • all calculations can only be done on numbers
  • all content display must be strings
  • counter(...) returns a string
  • counter can only be set to a constant integer, or incremented by a constant integer, not to a calculated one

Totally baffling. I've never seen a languge in my life which didn't have a way to print numbers, but that's how we got here.

Well, let's disregard all that and just make a series of bars of Fibonacci numbers size.

We'll need to do calculations with CSS variables not CSS numbers. CSS variables have access to their parent's variables, not their siblings, so we'll need to do some deep nesting. Also unfortunately while we can do some calculations on them, CSS properties aren't really ordered, so we cannot do multiple mutually dependent changes on one layer. So we'll use 3 nested spans per Fibonacci number. And as CSS doesn't have any global :nth-element-globally(3n), we'll give them specific classes.

It's possible that these limitations could be avoided, and we'll likely get CSS features that will let us code it in a nicer way (most likely number to string conversion).

Even with these limitations, I think it's still a neat result.

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <style>
    :root {
      --x: 0px;
      --y: 1px;
    }
    body {
      margin: 8px;
      font-size: 48px;
      min-height: 100vh;
    }
    span {
      display: block;
    }
    span.a {
      --z: calc(var(--x) + var(--y));
    }
    span.b {
      --x: var(--y);
    }
    span.c {
      --y: var(--z);
    }
    span.c::before {
      display: block;
      background-color: #480;
      height: 5px;
      width: var(--x);
      margin: 2px;
      content: "";
    }
  </style>
</head>
<body>
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
  <span class="a"><span class="b"><span class="c">
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Which looks like this:

Fib

There is definitely more

While this was a quick introduction to programming with CSS, this is by no means its limits.

People have been coding whole CSS-only games like this one or this one.

Will learning that make you a better frontend developer? Not really. But it is definitely fun!

Code

All code examples for the series will be in this repository.

Code for the CSS episode is available here.

Latest comments (3)

Collapse
 
robole profile image
Rob OLeary • Edited

Is it a bit unfair to put it in the "F Tier. Fun esoteric language, but definitely don't use for anything real. "?

Everybody uses CSS for styling pages, but for logic it is stretching the limits. It doesn't quite fit in with the rest of the test group here. Interesting read nonetheless

Collapse
 
taw profile image
Tomasz Wegrzanowski

I'd definitely recommend approaching CSS the same way you'd approach an fun esoteric language, and see how far you can take it. How else would you describe something like this?

Collapse
 
robole profile image
Rob OLeary • Edited

I understand where you are coming from. Building complete games in CSS is impressive. Certainly working with some constraints and adopting a particular mindset can produce something interesting. At the same time, I enjoy writing CSS for styling websites, so I will thread cautiously, I do not want to corrupt that!

I bookmarked this article to understand how to pull off logic in CSS yesterday, as happens!