By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 semantic-css.html

View raw Download
text/html • 13.35 kiB
HTML document, Unicode text, UTF-8 text
        
            
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8">
5
<title>
6
Let's write more semantic CSS
7
</title>
8
<link rel="stylesheet" href="/static/style.css">
9
<meta name="viewport" content="width=device-width, initial-scale=1.0">
10
</head>
11
<body>
12
<header>
13
<a href="#main" id="skip-link">Skip navigation</a>
14
<nav>
15
<ul>
16
<li><a href="/">Home</a></li>
17
<li><a href="/projects">Projects</a></li>
18
<li><a href="/index">Index</a></li>
19
<li><a href="/about">About</a></li>
20
<li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li>
21
</ul>
22
<ul>
23
<li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li>
24
</ul>
25
</nav>
26
</header>
27
<main id="main">
28
29
<h1>Let's write more semantic CSS</h1>
30
<div id="article-date">2024-05-18</div>
31
<p class="tags">
32
33
<a href="/index/web.html" class="tag">web</a>
34
35
<a href="/index/css.html" class="tag">css</a>
36
37
<a href="/index/html.html" class="tag">html</a>
38
39
</p>
40
41
<article class="content-area">
42
<p>You probably wrote something like this at least once in your life:
43
</p><pre data-language="html">&lt;div class="card card--rounded card--primary"&gt;
44
&lt;div class="card__image-container"&gt;
45
&lt;img src="image.jpg" alt="A nice image" class="card__image"&gt;
46
&lt;span class="card__image-caption"&gt;A nice image&lt;/span&gt;
47
&lt;/div&gt;
48
&lt;div class="card__content"&gt;
49
&lt;div class="card__header"&gt;
50
&lt;div class="card__title"&gt;Hello, world!&lt;/div&gt;
51
&lt;/div&gt;
52
&lt;p class="card__text"&gt;
53
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
54
&lt;/p&gt;
55
&lt;/div&gt;
56
&lt;div class="card__footer"&gt;
57
&lt;button class="btn btn--primary btn--raised btn--accent card__button card__button--primary"&gt;Click me!&lt;/button&gt;
58
&lt;button class="btn btn--secondary btn--raised btn--accent card__button card__button--secondary"&gt;Click me!&lt;/button&gt;
59
&lt;/div&gt;
60
&lt;/div&gt;
61
</pre><p>Or this:
62
</p><pre data-language="html">&lt;div class="max-w-sm rounded overflow-hidden shadow-lg"&gt;
63
&lt;div&gt;
64
&lt;img class="w-full" src="image.jpg" alt="A nice image"&gt;
65
&lt;span class="text-gray-500 text-base"&gt;A nice image&lt;/span&gt;
66
&lt;/div&gt;
67
&lt;div class="px-6 py-4"&gt;
68
&lt;div&gt;
69
&lt;div class="font-bold text-xl mb-2"&gt;Hello, world!&lt;/div&gt;
70
&lt;/div&gt;
71
&lt;p class="text-gray-700 text-base"&gt;
72
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
73
&lt;/p&gt;
74
&lt;/div&gt;
75
&lt;div class="px-6 py-4"&gt;
76
&lt;button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&gt;Click me!&lt;/button&gt;
77
&lt;button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded"&gt;Click me!&lt;/button&gt;
78
&lt;/div&gt;
79
&lt;/div&gt;
80
</pre><p>The second one is an adapted example from the <strong class="emphasis-2">Tailwind</strong> CSS docs. The first one is a variant that
81
uses <strong class="emphasis-2">BEM</strong> instead. Both of them have <em class="emphasis-1">many</em> problems.
82
</p><p>HTML has got over 100 elements you could use to structure your content. These examples use only
83
5: <code>div</code>, <code>span</code>, <code>p</code>, <code>img</code>, and <code>button</code>. This is not a problem in itself for small components,
84
but it can indicate one. Using <code>div</code> and <code>span</code> for everything means you're misusing HTML. This
85
is wrong: don't overlook HTML. JS or CSS may be more interesting, but the document language of
86
the WWW is HTML.
87
</p><p>The first example uses classes in place of elements. This creates extra work for both the HTML
88
and CSS author. The CSS still mirrors the HTML structure, and the HTML is much more verbose than
89
it needs to be. The word "button" or "btn" appears 8 times for each button. Ideally, it should
90
appear two times: once in the opening tag and once in the closing tag.
91
</p><p>The second example intentionally has the same markup tree as the first one. However, the classes
92
changed a lot. Tailwind uses classes instead of CSS rules. It leads to repetition. If you don't
93
want to repeat, you use components. But what if you don't do components? Then use <code>@apply</code> in
94
CSS. Yes, CSS. So you're basically writing CSS only with a different syntax and less flexibility.
95
</p><h2>A Simpler Way</h2><p>Let's strip the classes and focus on the markup tree for now. The two examples are identical in
96
this regard.
97
</p><pre data-language="html">&lt;div&gt;
98
&lt;div&gt;
99
&lt;img src="image.jpg" alt="A nice image"&gt;
100
&lt;span&gt;A nice image&lt;/span&gt;
101
&lt;/div&gt;
102
&lt;div&gt;
103
&lt;div&gt;
104
&lt;div&gt;Hello, world!&lt;/div&gt;
105
&lt;/div&gt;
106
&lt;p&gt;
107
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
108
&lt;/p&gt;
109
&lt;/div&gt;
110
&lt;div&gt;
111
&lt;button&gt;Click me!&lt;/button&gt;
112
&lt;button&gt;Click me!&lt;/button&gt;
113
&lt;/div&gt;
114
&lt;/div&gt;
115
</pre><p>Now you see what I said? This tree is not semantic at all. Let's find the appropriate elements for
116
each generic one.
117
</p><pre data-language="html">&lt;article&gt;
118
&lt;figure&gt;
119
&lt;img src="image.jpg" alt="A nice image"&gt;
120
&lt;figcaption&gt;A nice image&lt;/figcaption&gt;
121
&lt;/figure&gt;
122
&lt;section&gt;
123
&lt;header&gt;
124
&lt;h2&gt;Hello, world!&lt;/h2&gt;
125
&lt;/header&gt;
126
&lt;p&gt;
127
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
128
&lt;/p&gt;
129
&lt;/section&gt;
130
&lt;menu&gt;
131
&lt;button&gt;Click me!&lt;/button&gt;
132
&lt;button&gt;Click me!&lt;/button&gt;
133
&lt;/menu&gt;
134
&lt;/article&gt;
135
</pre><p>In case you're not familiar with the new elements, the quick meaning is:
136
</p><ul><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article">article</a> - a self-contained
137
piece of content that makes sense independently from the rest of the page
138
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure">figure</a> - a piece of content
139
that is referenced from the main content, but can stand alone
140
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption">figcaption</a> - a caption
141
for a <code>figure</code>'s other content (optional)
142
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section">section</a> - a thematic grouping
143
of content, typically with a heading
144
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header">header</a> - header for the
145
document or a smaller part of it, can include context, navigation or information about the
146
content
147
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2">h2</a> - a second-level section
148
heading (you probably knew this one already)
149
</p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu">menu</a> - a list of commands
150
available to take on a specific part of the content
151
</p></li></ul><p>Please read the MDN articles I linked if you want to know more about these elements.
152
</p><p>Depending on the other needs of your website or application, you will probably need to add a few
153
classes. However, unlike the other examples, classes should be used as little as possible. Let's
154
remember some things from the examples:
155
</p><ul><li><p>The article is supposed to be a card and styled as such.
156
</p></li><li><p>The first button is the primary action, and the second one is the secondary action.
157
</p></li></ul><p>In this site, let's say not all articles are cards, but since this one <em class="emphasis-1">is</em> a card, we'll
158
classify it as such. Let's also say that the secondary buttons are more common, this means we'll
159
add a class to the primary button and style that later.
160
</p><pre data-language="html">&lt;article class="card"&gt;
161
&lt;figure&gt;
162
&lt;img src="image.jpg" alt="A nice image"&gt;
163
&lt;figcaption&gt;A nice image&lt;/figcaption&gt;
164
&lt;/figure&gt;
165
&lt;section&gt;
166
&lt;header&gt;
167
&lt;h2&gt;Hello, world!&lt;/h2&gt;
168
&lt;/header&gt;
169
&lt;p&gt;
170
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
171
&lt;/p&gt;
172
&lt;/section&gt;
173
&lt;menu&gt;
174
&lt;button class="button-primary"&gt;Click me!&lt;/button&gt;
175
&lt;button&gt;Click me!&lt;/button&gt;
176
&lt;/menu&gt;
177
&lt;/article&gt;
178
</pre><p>Now, let's write a basic stylesheet for this. It won't look exactly like the second example for
179
the sake of simplicity, but it could easily be made to look like that. We're going to use a CSS
180
selector you've probably only seen in resets and to set the font on the <code>html</code> element, the
181
tag selector. We're also going to use some new CSS smarts to make the styles more maintainable.
182
</p><pre data-language="css">html, button, input, select, textarea {
183
font-family: system-ui, sans-serif;
184
}
185
article.card {
186
background-color: whitesmoke;
187
border-radius: 12px;
188
box-shadow: 0 0 4px #00000040;
189
display: flex;
190
flex-direction: column;
191
gap: 1rem;
192
overflow: hidden;
193
}
194
figure {
195
display: flex;
196
flex-direction: column;
197
gap: 0.25rem;
198
}
199
figure &gt; img {
200
width: 100%;
201
height: auto;
202
}
203
figcaption {
204
font-style: italic;
205
opacity: 0.875;
206
}
207
article.card &gt; section {
208
padding-left: 1rem;
209
padding-right: 1rem;
210
}
211
article.card &gt; menu, menu.buttonbox {
212
display: flex;
213
gap: 1rem;
214
justify-content: flex-end;
215
}
216
button, .button, /* provide alternative where it makes sense, since we may want to make something else look like a button */
217
input:is([type="button"], [type="submit"], [type="reset"]) {
218
background-color: white;
219
color: orange;
220
border: 4px solid currentColor;
221
padding: 0.5rem 1rem;
222
display: inline-flex;
223
align-items: center;
224
gap: 0.5rem;
225
border: none;
226
border-radius: 4px; /* Border radii are a decoration so pixels are fine */
227
}
228
:is(button, .button, input:is([type="button"], [type="submit"], [type="reset"])).button-primary {
229
background-color: orange;
230
color: white;
231
}
232
</pre><p>Observations:
233
</p><ul><li><p>We provide alternatives for some tag selectors where it makes sense, in case we want to make
234
something else look like a button. However, we don't force using both when it's already clear:
235
<code>&lt;button&gt;</code> will produce a styled button, same as <code>&lt;a class="button"&gt;</code>. <code>&lt;button class="button"&gt;</code>
236
is redundant.
237
</p></li><li><p>The <code>&gt;</code> child selector is used to avoid leaking styles in more complex nested layouts.
238
</p></li><li><p>We use the <code>:is()</code> pseudo-class to group selectors that have the same styles. This is a new
239
feature in CSS and it saves us from writing an enormous amount of combinations.
240
</p></li></ul><h2>Conclusion</h2><p>Now, writing HTML is much easier: the CSS will adapt to what you intended to describe. The CSS
241
is also much easier to maintain: the style can be changed easily without changing the HTML. The
242
elements are always styled automatically, and you can copy-paste a snippet of plain HTML
243
and have it magically match the rest of your site.
244
</p><p>A more complete framework for this could add some layout container utilities. For example, a
245
<code>grid</code> class that makes the element a grid container and uses <code>--width</code> and <code>--gap</code> custom
246
properties to position the children. There could also be layout <em class="emphasis-1">elements</em> to use in place of
247
divs like <code>x-hbox</code> and <code>x-vbox</code> that are flex containers. This would indicate the default style,
248
and an additional class or ID would be used to make them responsive as well. Utility classes
249
aren't bad, but they should be used for the things that can't cause repetition - which side a
250
dialogue should emerge from, or whether to add padding in a generic row container.
251
</p><h2>Frameworks Using Semantic CSS</h2><ul><li><p><a href="https://picocss.com/">Pico CSS</a> - does it very well, I should take some inspiration from it
252
</p></li><li><p><a href="https://watercss.kognise.dev/">Water.css</a> - a very minimalistic CSS framework, primarily intended
253
for publishing, but also includes interactive elements
254
</p></li><li><p><a href="https://andybrewer.github.io/mvp/">MVP.css</a> - a basic stylesheet for plain HTML made for any
255
site to look acceptable
256
</p></li><li><p>The roundabout also uses semantic CSS. Once the API is stabilised a little the CSS will be
257
released as a framework.
258
</p></li><li><p>You might not even need a framework.
259
</p></li></ul>
260
</article>
261
262
</main>
263
<footer>
264
<p>Page generated on Sunday, 4 May 2025 at 15:06:42</p>
265
<p xmlns:cc="http://creativecommons.org/ns#" >This work is marked with <a href="https://creativecommons.org/publicdomain/zero/1.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC0 1.0 Universal</a> (🄍). No rights reserved.</p>
266
<p>Hosted at <a href="https://roundabout-host.com/roundabout">Roundabout-host</a> using the static site service, and generated with <a href="/projects/ampoule.html">Ampoule</a>.</p>
267
<a href="#">Back to top</a>
268
</footer>
269
</body>
270
</html>