Once upon a time there was no CSS
When I started with web development in my early teenage years, CSS wasn't a big deal. While it was released in 1996 already it took a while to get widely adapted.
Website layouts were basically built with <table> tags and HTML attributes like
color, bgcolor, cellpadding and so on.
Back then HTML source code looked similar to this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>
<table>
<tr>
<td align="center" colspan="2">
<a href="/"><font color="red" face="Verdana">My website</font></a>
</td>
</tr>
<tr>
<td>
<u>SIDEBAR</u><br /><br />
<a href="/foo">Foo</a><br />
<a href="/bar">Bar</a>
</td>
<td>
<h1>Foo</h1><br /><br />
<font color="#00008b">Lorem ipsum</font>
</td>
</tr>
</table>
</body>
</html>
JavaScript was mostly used to apply some questionable effects to the mouse cursor - and to annoy people with popups and alert messages preventing you from leaving the website.
Popular software to write HTML code was Macromedia Dreamweaver and... my personal favorite... Frontpage Express.
Actually, things were quiet simple back then...
If you are old enough to remember these times, let us take a brief moment of introspection, shall we?
No SPA frameworks, preprocessors, package managers, dependency hells, super-feature-rich web browsers...
All the things we needed for web development were a simple text editor and an FTP upload tool...
...
OK, enough.
We would not be where we are today if the WWW and it's technologies haven't evolved.
Nowadays there are fancy E-commerce platforms, great games and billion-dollar enterprise applications running
in a browser - no matter what operation system you use. That's actually a great thing and would not have been possible
without the tools we have right now.
Then there came CSS... and inline styles
During the years the functionality of CSS increased, web browser got better in supporting it and therefore it got increasingly widespread.
While it was already possible to put your CSS stylesheets in a separate file or <style> block it wasn't too uncommon to simply use inline styles.
So this...
<font color="red">my text</font>
simply became this:
<span style="color: red;">my text</font>
The implementation of a linked navigation may have looked like this:
<nav>
<ul style="list-style-type: none;">
<li style="padding: 5px; ...">
<a style="text-decoration: none; color: darkblue; ..." href="...">Foo</a>
</li>
<li style="padding: 5px; ...">
<a style="text-decoration: none; color: darkblue; ..." href="...">Bar</a>
</li>
<li style="padding: 5px; ...">
<a style="text-decoration: none; color: darkblue; ..." href="...">Hello</a>
</li>
<li style="padding: 5px; ...">
<a style="text-decoration: none; color: darkblue; ..." href="...">World</a>
</li>
</ul>
</nav>
You can see, we have a lot of duplicate style information here. This basically had two downsides:
- Your HTML code got bigger, so it may be slower to load (We come to that point later again)
- If you want to change the style for multiple elements of the same type you had to touch all the places.
So, developers quickly agreed to use CSS classes instead...
Semantic HTML & CSS
When you decide to use CSS classes instead of inline styles you have to think about names for them.
There is the common approach to have your HTML structure as semantic as possible. That's good for crawlers
- because for machines structured, semantic HTML code is better to understand - and has evidential a good
effect to search engine rankings of the website.
To have good CSS class names & keep your HTML code semantic, the idea came up to give your CSS classes a semantic name, too. So, the naming of classes should not describe the styles itself (like "text-red", "btn-blue" etc.) but the element the classes are assigned to (like "sidebar"). CSS classes should only be used when necessary, to not clutter the code:
<nav class="sidebar">
<ul>
<li>
<a href="...">Foo</a>
</li>
<li>
<a href="...">Bar</a>
</li>
<li>
<a href="...">Hello</a>
</li>
<li>
<a href="...">World</a>
</li>
</ul>
</nav>
<style type="text/css">
nav.sidebar ul {
list-style-type: none;
}
nav.sidebar ul > li {
padding: 5px;
}
nav.sidebar ul > li > a {
text-decoration: none;
color: darkblue;
}
</style>
This was and still is considered a clean way to structure your HTML and implement your CSS classes.
The approach has a big downside though:
In bigger projects, the nesting level of CSS classes gets quite high.
High nesting levels not only make your CSS hardly readable - it's also way harder to maintain it: If you are required
to change the structure of your HTML later on you have to adjust the CSS code to the new structure.
Superset languages on top of CSS - like SCSS - make
your CSS, even with higher nesting levels, more readable to some degree - but they don't fix the actual problem.
I've worked on several dozen website projects that time.
How many projects do you think were strictly implementing semantic HTML and CSS?
?
?
Right. Zero.
The time and effort to "keep it clean" in a semantic way was simply not worth it.
Of course there are other approaches to have a clean CSS structure.
A popular one is BEM, wherein you don't try to keep the number of CSS classes as low as possible but instead classify every element you want to have a style into blocks, elements and modifiers.
<nav class="sidebar">
<ul class="sidebar__list">
<li class="sidebar__list-item">
<a href="...">Foo</a>
</li>
<li class="sidebar__list-item">
<a class="sidebar__list-item-link href="...">Bar</a>
</li>
<li class="sidebar__list-item">
<a class="sidebar__list-item-link href="...">Hello</a>
</li>
<li class="sidebar__list-item">
<a class="sidebar__list-item-link href="...">World</a>
</li>
</ul>
</nav>
BEM makes sure that the nesting level doesn't get too deep. Every element is inside a block and can have multiple modifiers. That's it.
Still, there are some downsides.
First of all it's not always clear what should be considered a block or element.
Secondly - as you can see in the example above - we again have a lot of code duplication again. Instead of style
attributes we now repeat CSS class names instead.
For sure BEM is a nice approach and for many cases superior than inline styles.
But isn't it a bit frustrating to have to decide between code duplication and crazy nesting levels?
Well, there's an alternative approach. And it's actually so obvious that I wonder why no one really came up with it before.
Tailwind CSS is implementing this new appraoch, even though - in the past - the author of Tailwind himself initially did praise semantic CSS.
The Tailwind approach
The idea behind Tailwind is to not reuse CSS class names (even though it's possible), but to reuse your HTML code.
Let's forget about CSS classes again and have another look at our navigation with inline styles:
<nav>
<ul style="list style">
<li style="the same item style">
<a style="the same link style" href="...">Foo</a>
</li>
<li style="the same item style">
<a style="the same link style" href="...">Bar</a>
</li>
<li style="the same item style">
<a style="the same link style" href="...">Hello</a>
</li>
<li style="the same item style">
<a style="the same link style" href="...">World</a>
</li>
</ul>
</nav>
The issue here is not only the duplication of the style attributes but the duplication of the very same
HTML tags again and again.
Even if you use CSS classes for every element you may need to touch every single element later again if the changes
you want to make have to be done in the HTML.
So what we could do instead is to create several template files
navigation.html:
<nav>
<ul style="list style">
{% include 'navitem.html' with {'href': '...', 'text': 'Foo'} %}
{% include 'navitem.html' with {'href': '...', 'text': 'Bar'} %}
{% include 'navitem.html' with {'href': '...', 'text': 'Hello'} %}
{% include 'navitem.html' with {'href': '...', 'text': 'World'} %}
</ul>
</nav>
navitem.html:
<li style="item style">
{% include 'link.html' with {'href': href, 'text': text} %}
</li>
link.html
<a style="link style" href="{{ href }}">{{ text }}</a>
Now, your CSS styles actually are being reused - but instead of just reusing CSS class names you reuse atomic HTML templates that contain CSS classes.
If you are using popular Single Page Application Frameworks like Vue.JS, React or Svelte, it is already quite common
to split your HTML (and it's corresponding JavaScript) into several components. And it's a good approach to do that
very granular.
But also for classic template engines like Twig it's perfectly doable to split your HTML code into several small components.
Actually the examples I just showed you were implemented in Twig.
So why not use just inline styles but a framework like Tailwind CSS instead?
Downsides of inline styles
Inline styles have a few downsides:
- they are very verbose and end up in very long style attribute values
- media queries are not possible, so you can't implement responsive web designs that look differently depending on the screen size
- there's no consistency regarding colors, margins, font sizes and so on
Tailwind provides you a huge number of CSS classes that work similar to inline styles but eliminate the downsides just mentioned.
Tailwind Example
While Tailwind class names are most likely still more verbose than those of other languages, it's much less
verbose than inline styles.
Instead of...
<a style="display:inline-block; color: red; font-weight: bold; padding: 4px;" href="{{ href }}">{{ text }}</a>
... it will look like this with Tailwind:
<a class="inline-block text-red font-bold p-1" href="{{ href }}">{{ text }}</a>
Tailwind Media Queries Example
Tailwind enables you to use media queries.
If you want to implement a grid that shows 1 column per row on mobile, 2 columns per row on tables and 3 columns per row on bigger screens you can do like this:
grid.html:
<div class="grid grid-cols-12 gap-2" >
{% include 'griditem.html' with {'content': 'foo '}%}
{% include 'griditem.html' with {'content': 'bar '}%}
{% include 'griditem.html' with {'content': 'baz '}%}
...
</div>
griditem.html:
<div class="col-span-12 md:col-span-6 lg:-col-span-4">{{ content }}</div>
CSS class configuration
Tailwind enables developers to follow a consistent style guide.
For example, if you Styleguide has a fixed list of colors that can or should be used in your design, you can adjust
your Tailwind configuration file so
Tailwind will automatically provide CSS classes for these colors. By default, Tailwind provides already
a huge list of predefined colors - but if you like you extend or replace them.
This does not only work for colors but nearly every CSS attributes you can imagine.
Performance
In early times it was considered good programming style to have your HTML code as small as possible.
Compared to semantic CSS the Tailwind approach definitely bloats up your HTML a bit.
A small HTML file size is not important anymore though because of several reasons:
- internet connections got faster - and a few more text characters won't make a difference
- compared to media files like images and videos, HTML document sizes are so small that they are most often negligible
- most web servers and browsers support GZIP or other compressions like Brotli. So HTML will be transfered compressed anyway.
Downsides of Tailwind CSS
Now that you know about the idea and benefits of Tailwind CSS I don't want to keep quiet about it's downsides.
Verbosity
As mentioned before, your HTML tag class attributes can and will be quite long when using Tailwind.
Lines like this will be no rarity.
<button class="inline-block py-3 px-5 bg-gradient-to-b from-yellow-500 to-orange-400 font-semibold text-white rounded-lg md:hover:scale-110 transition-all duration-300" ...>
If you really do not want to have such thing in your HTML, you could move the list of classes into another CSS file and combine it into a single one.
.primary-btn {
@apply inline-block py-3 px-5 bg-gradient-to-b from-yellow-500 to-orange-400 font-semibold text-white rounded-lg md:hover:scale-110 transition-all duration-300;
}
It's questionable though if this makes your code easier to understand if you have to look into several places (.html + .css file) to understand the implementation of your element's layout.
If you keep your (HTML) components small, having a few long lines of class lists might not be such a big deal breaker anymore.
You have to learn it first
Tailwind consists of a huge number of CSS class names that are only more or less loosely based on the CSS attribute they work with.
margin is m in Tailwind. padding is p, transform: scale(n) is scale-n and so on.
This is a bit of an entry threshold. To work productively with Tailwind from the beginning (and probably later on, too) I strongly recommend to have an extension
(VsCode or IntelliJ)
installed that will help you with autocompletion.
It might be also helpful to have the
official Tailwind documentation open in a browser tab so you are able
search for CSS attributes and find the corresponding Tailwind class.