1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
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:
```css
.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:
```css
.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:
```css
.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):
```css
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. 🕸️
References
[1] <https://uchu.style>
|