What are you trying? Are you happy with it?
(as a point of reference)
Your mileage will vary
Doesn’t mean we all need to
I think they are all pretty neat and I see the point…
— Chris Coyier
But I’m a bunch of years into this and still generally… write the selector I need and then style with that.
— Chris Coyier
Conventions won’t save us
Conventions can develop over time
I decided to stop using the word semantics. Instead I talk about the UX of HTML…
— Vasilis van Gemert, The UX of HTML
Instead of explaining the meaning of a certain element, I show them what it does.
— Vasilis van Gemert, The UX of HTML
.btn
» button
.btn-submit
» button[type=submit]
.link
» a:any-link
.hidden
» [hidden]
.active
» [aria-current]
aria-current
this-slide {
$my-color: red;
$my-color: yellow; /* winner! */
background-color: $my-color;
$my-color: green;
$my-color: powderBlue;
}
this-slide {
--my-color: red;
--my-color: yellow;
background-color: var(--my-color);
--my-color: green;
--my-color: powderBlue; /* winner! */
}
0+
declared values)0-1
cascaded value)1
specified value)1
computed value)1
used value)1
actual value)color: blue;
Building a list of declarations requires filtering our CSS to only the relevant declarations.
(determined one element at a time)
media
attr)<integer>
/ <number>
/ <dimension>
etc…<length>
/ <angle>
/ <time>
etc…<calc-sum>
<color>
/ <hue>
/ <alpha-value>
<image>
/ <position>
<string>
<url>
<custom-ident>
/ <dashed-ident>
html { color: 3em; }
3em
is a <length>
, not a <color>
html { color: teal; }
teal
is a <named-color>
, a <color>
sub-typehtml {
color: teal;
color: 3em;
}
html {
color: teal;
/* color: 3em; */
}
color
We’re Done! 🎉
html {
color: var(--my-color);
--my-color: teal;
--my-color: 3em;
}
.ಠ_ಠ {
--(╯°□°)╯: ︵┻━┻;
}
var()
function
Are not evaluated at parse time
var()
function if()
function --custom()
function
Are not evaluated at parse time
button { background: gray; }
.action { background: darkblue; }
[type=“submit”] { background: darkgreen;
background: var(--submit); }
#send { background: maroon; }
html {
background: red;
background: teal;
}
html {
/* background: red; */
background: teal;
}
html {
color: teal;
color: oklch(55% 0.09 195); /* discarded?? */
}
html {
color: teal;
/* color: oklch(55% 0.09 195); */
}
html {
color: teal;
color: oklch(55% 0.09 195);
}
html {
/* color: teal; */
color: oklch(55% 0.09 195);
}
You can use it and not use it at the same time, because it works and it doesn’t work at the same time. It’s Quantum CSS! It’s Magic!
/* broad defaults */
button { background: rebeccapurple; }
/* narrower patterns */
button.danger { background: maroon; }
/* one-off overrides */
button[type=submit] { background: forrestGreen; }
From p {}
through .text-center {}
Avoid even unique component styles
h2:has(> button[aria-expanded="false"]) + div {
display: none;
}
div
that comes after an h2
button
child, that is not expanded)/* expressive browser-ux */
form:has(:invalid) [type="submit"] { … }
/* we do all the work??? */
.form__btn--submit--invalid { … }
a[href*="://"] { /* external links */ }
a[href^="https:"] { /* secure links */ }
a[href$=".pdf"] { /* pdf links */ }
button[aria-pressed=true] { /* pressed buttons */ }
img:not([alt]) { /* images without alt text */ }
.flow > * + * {
margin-block-start: var(--flow-space, 1em);
}
No “intervention by the author”
* { /* universal */ }
p { /* type */ }
.summary { /* attribute */ }
#call-to-action { /* id */ }
* { /* universal */ }
button, div, span {
/* 'type' (element) */ }
::before, ::after, ::part() {
/* 'pseudo-element' */ }
.action, .summary {
/* 'class' */ }
:hover, :user-invalid {
/* 'pseudo-class' */ }
[type=“submit”] {
/* 'attribute' */ }
#send { /* 'id' */ }
/* 1 IDs, 2 attributes, 2 element type */
/* specificity: 1,2,2 */
form#contact button[type='submit']:active {
border-color: seaGreen;
}
border-color
declaration has a selector1, 2, 2
[
1
,0,2]
vs [0,3,2] vs [0,2,3]
[0,
3
,2]
vs [0,
2
,3]
#example { /* [1,0,0] */ }
[id="example"] { /* [0,1,0] */ }
.example { /* [0,1,0] */ }
.example.example.example { /* [0,3,0] */ }
a {
&:any-link { color: blue; }
&:focus, &:hover, &:active {
color: deepPink;
}
}
.card {
@media (width > 20em) {
display: flex;
}
}
article {
h2 { … }
.thumbnail { … }
}
@scope
ol, ul {
> p { /* :is(ol, ul) > p */ }
.sidebar & { /* .sidebar :is(ol, ul) */ }
}
:is()
under the hood/* :is(button, .btn, #my-btn) */
button, .btn, #my-btn {
&:focus,
&:hover,
&:active { /* 1,1,0 */ }
}
:is()
Selector:where()
Selector<nav>
<a href="#">not selected</a>
<a href="#" class="active">selected!</a>
</nav>
nav a:is(.active)
nav a:where(.active)
:is()
& :where()
work the same:is()
and :where()
selectors:where()
Removes Specificity
/* nav a.active { 0,1,2 } */
nav a:where(.active) { /* 0,0,2 */ }
a:where(nav .active) { /* 0,0,1 */ }
*:where(nav a.active) { /* 0,0,0 */ }
:is()
takes
Highest Specificity
:is(a, .b, #c .d) { /* 1,1,0 */ }
a:is(.b, #c .d) { /* 1,1,1 */ }
a:where(#logo, .sponsor .logo) {
/* specificity: 0,0,1 */
}
a:is(#logo, .sponsor .logo) {
/* specificity: 1,0,1 */
}
<a class="sponsor logo">Still 1,0,1</a>
a:is(#fakeID, .sponsor .logo) { /* … */ }
(but)
(across large teams & projects)
(through lower boundaries & proximity)
.title { /* global */ }
.post .title { /* nested */ }
.post__title { /* BEM */ }
@scope
(
<root>
) { … }
Always child or descendant
BEM, CSS Modules, Vue, JSX, Stylable, etc
.post__title { /* BEM */ }
.title[data-JKGHJ] { /* Vue */ }
@scope (<root>) to (
<boundary>
) {…}
@scope (.media) to (.content) {
img { /* only images that are "in scope" */ }
}
<article>
<style scoped>
p { color: green; }
</style>
<p>This paragraph will be green.</p>
</article>
<p>This paragraph won't!</p>
<article>
<style>
@scope { p { color: green; } }
</style>
<p>This paragraph will be green.</p>
</article>
<p>This paragraph won't!</p>
@scope (.block-name) to (.block-content) {
.title { /* only inside the block! */ }
}
button {
&:hover { background: hotPink; }
&[aria-pressed=true] { border: thick solid teal; }
@media (width > 30em) { padding-inline: 1em; }
& + & { border-inline-start: 1ch; }
}
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
@scope (.light-theme) {
a { /* similar to simple nesting… */ }
}
@scope (.dark-theme) {
a { /* but the _closer_ scope root wins… */ }
}
@layer settings { … }
@layer tools { … }
@layer generic { … }
@layer elements { … }
@layer objects { … }
@layer components { … }
@layer overrides { … }
demo:
/* establish layer order */
@layer one, two, three;
/* add code to layers as needed */
@import url(two.css) layer(two);
@layer three { … }
@layer one { … }
@layer two { … }
@layer components {
@layer state { … }
}
/* access nested layers */
@layer components.state { … }
/* system.css */
@layer theme { … }
@layer components { … }
@import url(system.css) layer(system);
@layer system.theme { … }
@layer system.components { … }
@import url(system.css) layer(system);
@layer system.theme { … }
@layer system.components { … }
@layer system.custom { … }
@import url(bootstrap.css) layer(bootstrap.theirs);
@layer bootstrap.ours {
/* anything here will override bootstrap */
}
@layer components {
@layer defaults, themes, state;
}
my recommendations
/* add as the first styles */
@layer reset, framework, components, utilities;
<style>/* keep this before linked styles */
@layer reset, framework, components, utilities;
</style>
<link rel="stylesheet" href="…">
<link rel="stylesheet" href="…">
<style>
@layer reset, framework, components, utilities;
@import url(…) layer(reset);
@import url(…) layer(framework);
</style>
*Including inter-org dependencies
@import url(bootstrap.css) layer(bootstrap.vendor);
@layer bootstrap.overrides {
/* anything here will override bootstrap */
}
<template>…</template>
<style>
@layer components {
/* … */
}
</style>
*the styles will be hidden from older browsers
!important
->
Layers ->
Importance
@layer reset {
[hidden] { display: none !important; }
}
styling web components
Context
in the cascade/* shadow DOM styles */
:host(my-element) { outline: thin solid red; }
[part=title] { outline: thin dashed red; }
::slotted(span) { outline: thin dotted red; }
/* light DOM styles */
my-element { outline: thick solid green; }
my-element::part(title) { outline: thick dashed green; }
my-element > span { outline: thick dotted green; }
similar to user agent origin
!important
to protect essential styles,and reverses priority
(demo if needed)
in transitions and animations
hotPink
»
teal
hotPink
rgb(93 138 220)
?!
teal
hotPink
and teal
button {
background: teal;
transition: background 1s;
}
button:hover { background: hotPink !important; }
:target { animation: bg-change 3s ease-in both; }
@keyframes bg-change {
from { background: pink; }
to { background: cyan; }
}
animation
property is in the cascadeJust to restate:
<img src="..." width="160px" height="90px" hidden>
<s>strikethrough</s>
width
, or height
, or hidden
for each property of each element