Terence Eden’s Blog<p><strong>Class Warfare! Can I eliminate CSS classes from my HTML?</strong></p><p><a href="https://shkspr.mobi/blog/2025/09/class-warfare-can-i-eliminate-css-classes-from-my-html/" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://</span><span class="ellipsis">shkspr.mobi/blog/2025/09/class</span><span class="invisible">-warfare-can-i-eliminate-css-classes-from-my-html/</span></a></p><p></p><p>I recently read a brilliantly provocative blog post called "<a href="https://aaadaaam.com/notes/no-class/" rel="nofollow noopener noreferrer" target="_blank">This website has no class</a>". In it, Adam Stoddard makes the case that you might not need CSS classes on a modern website:</p><blockquote><p>I think constraints lead to interesting, creative solutions […]. Instead of relying on built in elements a bit more, I decided to banish classes from my website completely.</p></blockquote><p>Long time readers will know that I'm a big fan of using semantic HTML where possible. If you peek beneath the curtain of this website you'll only see a handful of <code><div></code> elements (mostly because WordPress hardcodes them) - all the other blocks are fully semantic. Regrettably, there are rather too many <code><span></code> elements for my liking - normally for accessibility or for supplementing the metadata.</p><p>Overall, my CSS contained about 134 rules which selected based on class. Is that a lot? It <em>feels</em> like a lot.</p><p>On the one hand, classes are an easy way of splitting and grouping elements. Some <code><img></code>s should be displayed one way, the rest another. There's no semantic way to say "This is a hero image and should take up the full width, but this is an icon and should float discretely to the right."</p><p>But, on the other hand, <em>why</em> do we need classes? Keith Cirkel's excellent post "<a href="https://www.keithcirkel.co.uk/css-classes-considered-harmful/" rel="nofollow noopener noreferrer" target="_blank">CSS Classes considered harmful</a>" goes through their history and brings together some proposed solutions for replacing them. I think his idea of using <code>data-</code> attributes is a neat hack - but ultimately isn't much different from using classes. It's still a scrap of metadata to be tied into a style-sheet.</p><p>Classes are great for when you <em>reuse</em> something. I have multiple <code><section></code> elements but most don't share anything in common with the others. So they probably oughtn't have classes.</p><p>Removing classes has some advantages. It makes the HTML fractionally smaller, sure, but it also forces the author to think about the logical structure of their page and the semantics behind it.</p><p>Looking through my HTML, lots of classes exist because of laziness. If I want to position all the <code><time></code> elements which are within a comment, I don't <em>need</em> to write <code><time class="whatever"></code> and to pair it with <code>.whatever { … }</code>. Instead, I can use modern CSS selectors and say <code>#comments time { … }</code>.</p><p>But this leads me on to another existential question.</p><p><strong>Are IDs necessary in modern HTML?</strong></p><p>Mayyyyybe? I only have one <code><main></code> element, so an ID on there is unnecessary. <code><input></code> elements need IDs in order to be properly targetted by <code><label></code>s - but the label can wrap around the input. I have multiple <code><aside></code> elements because there's no semantic <code><widget></code> element, so they need unique IDs.</p><p>In theory, as suggested by Adam above, I could use an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" rel="nofollow noopener noreferrer" target="_blank">autonomous custom element</a> like <code><my-widget></code> - but that has none of the semantics and, frankly, feels like a bit of a cheat.</p><p><strong>Trimming the fat</strong></p><p>Any day where I can delete some code is a good day. This was an excellent exercise in going through (years) of HTML and CSS to see what cruft had built up.</p><p>The first CSS rule I changed was, as mentioned above:</p><pre><code>time.commentmetadata { float: right;}</code></pre><p>Which became:</p><pre><code>#comments time { float: right;}</code></pre><p>Classless and slightly more brief. Is it more readable? Having the fact it was about the metadata in a class could have been slightly useful - but if I thought it would be confusing, I could stick a <code>/* comment */</code> in there.</p><p>Next, I found <code><nav class="navigation posts-navigation"></code> - what a tautology! I have multiple <code><nav></code> elements, it is true. But none of them have the same style. So this swiftly became <code><nav id="posts-navigation"></code> with an accompanying CSS rewrite.</p><p>My theme switcher had a bunch of <code><label class=button></code>s. They were all within a container with a unique ID, so could they be changed? Yes. But seeing the class name in the HTML is a good reminder to the author of <em>how</em> they are meant to display. Does that co-mingle content and presentation too much?</p><p>Some of the WordPress default classes are ridiculous. The <code>body_class()</code> function injected this into every <code><body></code></p><p><code>"wp-singular post-template-default single single-post postid-62959 single-format-standard wp-theme-edent-wordpress-theme"</code></p><p>Most of that is redundant - what's the difference between single and single-post? For my purposes, nothing! So they were all yeeted into the sun.</p><p>Rather than targetting IDs or classes, I targetted the presence or absence of Schema.org microdata.</p><p>For example:</p><pre><code>main[itemprop="blogPost"] { … }main:not([itemprop="blogPost"]) { … }</code></pre><p>This can go to the extreme. I have lots of comments, each one has an author, the author's details are wrapped in <code><div class="authordetails">…</div></code></p><p>That can be replaced with:</p><pre><code>/* Comment Author */li[itemtype="https://schema.org/Comment"] > article > div[itemprop="https://schema.org/author"] { margin-bottom: 0;}</code></pre><p>Is that <em>sensible</em>? It is more semantic, but feels a bit brittle.</p><p>Parent selector are also now a thing. If I want a paragraph to have centred text but <em>only</em> when there's a submit button inside it:</p><pre><code>p:has(input#submit) { text-align: center;}</code></pre><p>Again, am I sure that my button will always be inside a paragraph?</p><p>Similarly, <a href="https://css-tricks.com/child-and-sibling-selectors/" rel="nofollow noopener noreferrer" target="_blank">sibling selectors</a> are sometimes superior - but they do suppose that your layout never changes.</p><p><strong>What remains?</strong></p><p>There are some bits of this site which are reusable and do need classes. The code-highlighting you see above requires text to be wrapped in spans with specific classes.</p><p>Image alignment was also heavily class based.</p><p>There are some accessibility things which are either hidden or exposed using classes.</p><p>A bunch of WordPress defaults use classes and, even if they are redundant, it's hard to exorcise them.</p><p>As much as I would have liked to get rid of all my IDs, many needed to stay for linking as well as CSS targetting.</p><p>All told, the changes I made were:</p><ul><li>134 class selectors down to about 65.</li><li>35 ID selectors up to about 50.</li><li>5 attribute selectors up to to about 20.</li><li>Deleted or combined a lot of redundant CSS and tidied up my markup considerably.</li></ul><p>I have around 250 CSS rules, so now the majority target semantics rather than classes or IDs.</p><p><strong>Is this really necessary?</strong></p><p>No, of course not. This is an exercise in minimalism, creativity, and constraint. Feel free to litter your HTML with whatever attributes you want!</p><p>As I went through, it increasingly became apparent that I was fitting my CSS to my HTML's logical structure rather than to its <em>conceptual</em> structure.</p><p>Previously, my comments were targetted with a class. Now they have the slightly more tangled targetting of "divs with this schema attribute whose parent is an article and whose grandparent has this ID".</p><p>It is a delightful meditative exercise to go through your code and deeply consider whether something is unique, reusable, or obsolete.</p><p></p><p><a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/blog/" target="_blank">#blog</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/css/" target="_blank">#css</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/html/" target="_blank">#HTML</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/schema-org/" target="_blank">#schemaOrg</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/semantic-web/" target="_blank">#semanticWeb</a></p>