The winner of $100 is āWho Needs Prettier When You Have CSS?ā by @aqandrew. The solution for the problem is just š¤Æ. I would have never thought about using CSS shape masks to be able to reformat your code. ššš https://t.co/mqquyKHFfO pic.twitter.com/a7vBGkLzuU
ā vjeux āŖ (@Vjeux) February 28, 2024
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:
- solve an interesting problem by writing a small greenfield project
- potential to win a cash prize
- donāt have to pull an all-nighter or drink Soylent
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
- write/paste JS code in a textarea
- lay out syntax-highlighted code inline
- 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:
- shape-outside on MDN
- shape-outside on CSS-Tricks
- Take A New Look At CSS Shapes, from Smashing Magazine
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
-
Like SVG/canvas, the coordinate system for
polygon()
has (0, 0) at the top left, x is positive toward the right, and y is positive toward the bottom. -
From the CSS-Tricks article above:
if [the
shape-outside
property] is going to be animated it requires the same number of vertices when you declare the animated stateIn my submission, each of the lightbulb stencils I used was defined by 14 coordinates, so I had to define each of the the inactive stencils with 14 coordinates too. The same goes for the triangles demo (3 coordinates per stencil).
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
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.
This says āmake the .PrettyShapeArea
61 characters wide and 40 lines tallā.
nesting
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
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
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.
Additional notes
-
Flexbox was already widely supported by the time I started learning web development, so Iāve never really had a reason to use floats until now.
-
My submission is a React app, but only because thatās the UI framework Iām most comfortable with. The only state variable is the code string, which gets passed to Prism.js for syntax highlighting.
-
I usually develop in Chrome. Most of the time I spent on my submission was fiddling with coordinates in
polygon()
to get a nice-looking light bulb shape. 10 days after I submitted my solution, I saw that Firefox DevTools has a convenient GUI for editing shapes:I use it for clip path I think they call it the shape editor. This is what it looks like. Itās super handy! pic.twitter.com/TjJKAyyI2i
ā Mandy Michael (@Mandy_Kerr) February 29, 2024
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:
- subgrid
- container queries
- scroll-driven animations
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. āš½