[homepage|cv] WM-086 [text|html] [remarks]
              
Document: WM-086                                                 P. Webb
Category: Programming                                         2026-03-24

                            How to write CSS

Abstract

   CSS is about structure and design, the code should reflect that.

Body

   I’ve been writing HTML and CSS for 20+ years at the time of this
   writing. Along the way I’ve learned, discovered, and refined the way
   I write CSS. In 2026, I’m so glad I don’t need Sass anymore, as much
   as I enjoyed writing it; nested code and variables are now readily
   available in browsers without a precompilation step. Success!

   However, I still run into codebases where the CSS irritates tf outta
   me. I’m not just talking random projects on Github, I mean in Fortune
   500 companies too. LLMs regurgitate bad practices so for all the new
   coders discovering the joy and beauty of web development, here’s some
   tips from someone who survived the BEM syntax era and completely
   side-stepped the "let’s just chain variables" frenzy that’s still
   pervasive today.

   What follows are examples of what I consider to be poorly written CSS
   and how I fix them:

   

   .board_icon {
   	 text-align: center;
   	 padding: 8px 4px 0px 4px;
   	width: 60px;
   	flex-shrink: 0;
   }
   .board_icon a {
   	display: inline;
   }
   .board_icon a:hover, .board_icon a:focus{
   	text-decoration: none;
   }
   .board_icon a::before {
   	 display: inline;
   	 font-family: "Font Awesome 6 Free";
   	 font-size: 2em;
   	 content: "\f086";
   }
   .board_icon a.board_on::before {
   	 font-weight: 900;
   }
   .board_icon a.board_on2::before {
   	font-weight: 900;
   }
   .board_icon a.board_off::before {
   	font-weight: 400;
   }
   .board_icon a.board_redirect::before {
   	font-weight: 900;
   	content: "\f061";
   }

This physically pains me (if I look at it too long). Inconsistent indentation and lack of space between rules are the most egregrious errors in this for me, but you also have rules that can be consolidated and a unit specifier on a zero value (0px is unnecessary, just use 0). Finally, the rules aren’t in alphabetical order. Computers and browsers don’t care but for humans, code you can scan quickly is important for collaboration; even if that collaborator is future you! Don’t you wanna make future you’s life at least a lil’ bit easier? Here’s how I would rewrite that code block:

   .board_icon {
     flex-shrink: 0;
     padding: 8px 4px 0 4px;
     text-align: center;
     width: 60px;

     a {
       display: inline;

       &::before {
         content: "\f086";
         display: inline;
         font-family: "Font Awesome 6 Free";
         font-size: 2rem;
       }

       &:focus,
       &:hover {
         text-decoration: none;
       }

       &.board_off::before {
         font-weight: 400;
       }

       &.board_on,
       &.board_on2,
       &.board_redirect {
         &::before {
           font-weight: 900;
         }
       }

       &.board_redirect::before {
         content: "\f061";
       }
     }
   }

You might’ve noticed I replaced the 2em font-size with 2rem. This is more of an aesthetic choice. Elastic Measure (em) and Root Elastic Measure (rem) are similar in that they scale based on something but em scales based on the parent element whereas rem scales based on the root (html) font size. When I’m building sites, I want everything to be cohesive and scale uniformly. That way, when I decide to change the root font size, my entire site won’t look wonky. Here’s a list of other things I’ve seen in the particular codebase I’m rewriting for my forum theme: - margin:0 0 10px 0;: no space after the colon - border-color :rgb(199, 195, 191);: space before the colon but not after? Why? - border-top: 1px solid RGB(255, 255, 255);: why is RGB all caps here but not in the previous line? Why use rgb at all when this could be represented as #fff or simply white? - font-weight: bold; and font-weight: 700;: these both mean bold and there’s only system fonts declared so why specify 700 and not 600 (the default)? - margin-top: 5px !important;: if you have to use !important;, something’s wrong. It’s possible this theme is trying to override some styling of the core forum software so I’m willing to let this slide but then again, proper nesting would eliminate the need for this. - background: #fdfdfd;: unless you also have a background image and positioning, you should always use background-color. Specificity wins. - font: 9px/15px verdana, sans-serif;: I don’t like this for a few reasons. First, font-size and line-height are just easier to read and should be declared in parent elements. Per element rules like this leads to eventual divergence and tech debt. Second, the font name is lowercase here and regular case elsewhere. Like so: font-family: Verdana, Helvetica, Arial, sans-serif; and this is applied to an h1, which makes sense to have a different font as it’s a headline. I rarely use font and the rare times I do, it’s to do font: inherit (browsers by default have buttons and inputs use different fonts). - padding: 10px 10px;: redundant; this is telling us that there’s 10 pixels of padding to the top and bottom, as well as left and right. This could be rewritten as padding: 10px; (10 pixels of padding all around). Now, there are certain conditions where I don’t necessarily use alphabetical order and that’s when there are rule pairs present. - width / height - margin / padding - top / left / bottom / right Here’s a simple example:

   .profile_hd {
     width: 2rem; height: 2rem;

     &::before {
       width: 100%; height: 100%;

       background-image: url("../images/emoji/bust_in_silhouette_3d.png");
       background-size: contain;
     }
   }

And a more involved one (using CSS variables from my palette[1]). I left the color: rgb rule in there because I haven’t decided what to replace it with (I don’t like mixing color rules, stay consistent… similarly, I’m not sure that margin-top needs to be there…oh and &::before and &::after are grouped together in that order because it makes sense):

   ul {
     background-color: var(--uchu-yang);
     border: 1px solid var(--uchu-gray-3);
     border-radius: 4px;
     box-shadow: 3px 3px 4px oklch(var(--uchu-yin-raw) / 30%);
     color: rgb(83, 100, 130);
     line-height: 2.2rem;
     margin-top: 2px;
     min-width: 18.2rem;
     padding: 0.5rem;
     position: absolute;
     z-index: 90;

     &::before,
     &::after {
       width: 0; height: 0;

       border-left: 0.5rem solid transparent;
       border-right: 0.5rem solid transparent;
       content: "";
       position: absolute;
     }

     &::before {
       top: -0.5rem; left: 1.25rem;
       border-bottom: 0.5rem solid var(--uchu-yang);
     }

     &::after {
       top: calc(calc(0.5rem + 1px) * -1); left: 1.25rem;

       border-bottom: 0.5rem solid var(--uchu-gray-3);
       z-index: -1;
     }
   }

You can see width and height together at the top of a rule block, separated by a blank line because there are multiple rules after that. However, in the standalone &::before block, there’s no blank line after top and left because that’d look silly. The &::after block has more than one rule after top and left so those lines are grouped together. I’ve dabbled in trying to get Prettier to format my CSS files back when I was in the Node.js ecosystem, with middling results. I’m sure I could get Claude to make a formatter to my specifications…hmm, side project for now. There are situations where I may have something like padding: 1rem; and also have margin-right: 2rem. I wouldn’t put these together because of the -right. Non-dashed specifiers are in alphabetical order like everything else (including padding in this instance). This codebase has a lot of styling on IDs, which is something I don’t do. For me, IDs are for HTML and JavaScript, not styling; either use the element name and style against that or apply a class to said element. For naming elements, I prefer using a dash or two and relying on nesting (no more than three levels) when necessary. For this project, I’m beholden to the existing HTML syntax in PHP files. They’ll get updated over time. I’m probably missing a lot but this is just off the top of my head. Multi-trillion-dollar corporations perpetuate these terrible code choices too but at least in my personal projects I can have a curated and maintainable experience. 🕸️