Formatting Code with Old and New CSS

Published
I won a coding challenge last month! šŸ„³ Here are my notes about some useful CSS I learned along the way.

In January 2024, Vjeux began holding a series of weeklong coding challenges called Algorithm Arena. I was drawn to this challenge to format code into a cool shape for these reasons:

An idea behind this challenge was to play around with layout algorithms, and one of the things Vjeux is known for is driving the widespread adoption of Prettier for formatting code. So my initial plan was to write a Prettier plugin to implement layout algorithms. Then I realized it would be a lot less work to hand off the layout algorithms to CSS instead. Plus, this was a good opportunity to practice some CSS features I hadnā€™t tried before.

You can check out my coding challenge submission on CodeSandbox.

My approach

Overview

  1. write/paste JS code in a textarea
  2. lay out syntax-highlighted code inline
  3. create the desired shape by setting shape-outside on invisible floated elements next to the code

Old CSS

The majority of the weight here is being pulled by shape-outside. I think this property was introduced in 2014. The way it works is that if you set set float and shape-outside on an element and place it next to content with display: inline like paragraphs, the inline content will flow around the shape.

Lots of good articles have been written on how to use this property to wrap paragraphs around arbitrary shapes:

A simplified example

Letā€™s say we want a paragraph to lay out in the shape of a triangle with a horizontal top side (ā–¼). The negative space in our text block will take the shape of right triangles with a horizontal bottom side. These can be defined with shape-outside: polygon(ā€¦). So we float two of these triangles to the left and right of the text:

See the Pen CSS shape-outside-demo (static) by Andrew Aquino (@aqandrew) on CodePen.

Then we can set the transition property on the triangles to get smooth animations:

See the Pen CSS shape-outside-demo (dynamic) by Andrew Aquino (@aqandrew) on CodePen.

Notes

New CSS

shape-outside is a neat little property, but I believe the more interesting CSS magic is happening elsewhere in the demos. If you dig into the CodeSandbox/CodePen links above, you might notice some unfamiliar CSS.

Iā€™d recently watched a video where Kelvin Omereshone interviewed Adam Argyle, a CSS developer advocate for Google Chrome. During the interview, Adam demonstrated some modern additions to CSS that can eliminate the need for a preprocessor like Sass. He had this to say about learning CSS in his closing remarks:

ā€¦ if you focus on a couple of the cool features that you see in some interfaces ā€¦ go and rebuild it, and find that inspiration, and work yourself through all the different steps that it takes to get that to reach that level of quality that youā€™re looking for, and then move on to the next thing. And as you continue building high quality interfaces where youā€™re spending a lot of time on the little details, all the little details will stick to you, and theyā€™ll travel with you through all your future projects, and youā€™ll just kind of snowball and gain skills and get better and better.

Inspired by all of the quality-of-life CSS features shown in the rest of the video, I decided to see if I could leverage them to help solve this challenge:

ch and lh units

Introduced in 2018

These can make your styles more expressive if youā€™re defining dimensions in terms of characters. ch turned out to be ideal for a code formatting UI, since the text being formatted was in a monospaced font.

.PrettyShapeArea {
width: 61ch;
height: 40lh;
}

This says ā€œmake the .PrettyShapeArea 61 characters wide and 40 lines tallā€.

nesting

Introduced in 2023

Native CSS nesting alone is enough for me to drop Sass and CSS-in-JS for most personal projects. Itā€™s the main reason Iā€™ve historically used the former to keep styles DRY. (Iā€™ll quit using Sass entirely if/when we gain the ability to put CSS variables in media queries.)

cascade layers

Introduced in 2022

@layer base, lib, app;

The layers in this rule are listed from least precedence (left) to greatest precedence (right). So the app styles will always override lib styles, which will always override base styles.

See the Pen @layer overrides by Andrew Aquino (@aqandrew) on CodePen.

Cascade layers enable you to more clearly define the precedence of styles without resorting to !important, which is a big win for maintainability.

For small demos that use a single stylesheet like my submission, I can place the most salient styles at the top of the CSS file (@layer app, which contains shape-outside declarations), and tuck away minor presentational styles at the bottom (@layer base, which contains rules like input { cursor: pointer; }). This pattern is helpful for both me as an author and anyone who inspects the CSS to see how it works.

:has() selector

Introduced in 2023

I admit I didnā€™t understand what the big deal was when the :has() selector was released last year. Then I saw in that interview that you can use it to change styles based on form state without writing any JS.

/* initialize the left stencil's shape to a zero-width rectangle */
.stencil.left {
shape-outside: var(--shape-left-inactive);
}
/* when the formatting checkbox is checked, give the stencil a new shape */
.App:has(#format:checked) .stencil.left {
shape-outside: var(--shape-left);
}

Additional notes

Conclusion

Use the platform! CSS is really good at layout algorithms.

ch/lh units, nesting, cascade layers, and the :has() selector are all wonderful additions to CSSā€”not simply because theyā€™re new and shiny. The power of these modern features is that they make it easier to build cool stuff.

At the time of writing, there are a bunch of others that I still want to try out, including:

Many more are listed in Chrome for Developersā€™ CSS Wrapped 2023.

Just like with this coding challenge, Iā€™ll be taking opportunities to work these new CSS features into my projects so I can continue to grow my skills. āœŒšŸ½