/* a 'declaration' */
property: value;
/* a 'block' */
{
background: white;
color: mediumVioletRed;
}
<!-- 'inline' (or 'element attached') styles -->
<p style="
background: white;
color: mediumVioletRed;
">…</p>
<p style="color: mediumVioletRed;">…</p>
<p style="color: mediumVioletRed;">…</p>
<p style="color: mediumVioletRed;">…</p>
<p style="color: mediumVioletRed;">…</p>
<p style="color: mediumVioletRed;">…</p>
…A simple mapping between HTML elements and presentation hints.
— Håkon Lie, Cascading HTML style sheets – a proposal
p
{ /* … */ }
/* a 'style rule' (or 'rule set') */
p {
background: white;
color: mediumVioletRed;
}
<p>This is a short paragraph</p>
p { … }
<!-- a 'style sheet' -->
<link rel="stylesheet" href="styles.css">
/* another 'style sheet' */
@import url('styles.css');
An ordered list (cascade) of style sheets.
— Håkon Lie, Cascading HTML style sheets – a proposal
<button type="submit" id="send" class="action">
button { background: gray; }
.action { background: darkBlue; }
[type=“submit”] { background: darkGreen;
background: var(--submit, black); }
#send { background: maroon; }
<button>
Needs
One Background-Color
Color
Padding-Left
Margin-Inline-End
color
,padding-left
,margin-inline-end
, etc.exactly one per property, per element
(Read it from the specification)
.short { border: thin double; }
.long {
border-top: thin double;
border-left: thin double;
border-bottom: thin double;
border-right: thin double;
border-image: none;
}
border-image
Border
is shorthand for setting all of them.short { border: thin double; }
.long {
/* declared */
border-top-width: thin;
border-top-style: double;
/* reset to 'initial' value */
border-top-color: currentColor;
border-image: none;
}
width
, style
, and color
of eachcurrentColor
border-image
which is reset-only(zero or more)
demo (debug mode):
The user/browser specifies initial preferences…
— Håkon Lie, Cascading HTML style sheets – a proposal
demo:
/* initial values */
display: inline;
background: transparent;
color: CanvasText;
font-style: normal;
flex-basis: auto;
/* etc. */
display:inline
, even on div
s…body { margin: 8px; }
a:link {
color: blue;
text-decoration: underline; }
h1, h2, h3, h4 /* etc */ {
font-weight: bold;
font-size: something big I dunno; }
8px
margin on the body
resource://gre-resources/
Establish desired preferences
[CSS] provides author and reader with the same notation…
— Håkon Lie, Cascading HTML style sheets – a proposal
Settings / Advanced / Style sheet…
about:preferences
chrome://settings/appearance
If conflicts arise the user should have the last word…
— Håkon Lie, Cascading HTML style sheets – a proposal
!important
to a declaration…!important
mirror universeresource://gre-resources/
demo:
!important
behaviorWith our own colleagues and libraries
From higher origins breaking important things
in transitions and animations
hotPink
»
teal
hotPink
rgb(93 138 220)
?!
teal
rgb(93 138 220)
button {
background: plum;
transition: background 1s;
}
button:hover { background: pink; }
transition
property also cascades:target { animation: bg-change 3s ease-in both; }
@keyframes bg-change {
from { background: pink; }
to { background: cyan; }
}
animation
property itself cascades!important
keyframes [present, debug]demo:
!important
keyframe declarations are ignored!important
will winstyling web components
demo:
demo:
similar to user agent origin
>
shadow
and reverses priority
<
shadow
(demo)
<!-- Element-attached/inline -->
<button style='color: blue'>…</button>
/* Selector-Mapped Styles */
button { color: violet; }
they don’t reverse!
p { /* … */ }
a { /* … */ }
.summary { /* … */ }
:hover { /* … */ }
#call-to-action { /* … */ }
* { /* 'universal' */ }
button, div, span {
/* 'type' (element) */ }
::before, ::after, ::part() {
/* 'pseudo-element' */ }
.action, .summary {
/* 'class' */ }
:hover, :user-invalid {
/* 'pseudo-class' */ }
[type=“submit”] {
/* 'attribute' */ }
#send { /* 'id' */ }
*
{ outline: …; }
#logo
{ outline: …; }
*
(go first)type
s (p
, ul
, body
, etc).class
es & [attributes]
#ID
s (most power)p.summary:focus-within { /* … */ }
a:hover { /* … */ }
> +
(space)
~
p.summary a:hover { /* descendant */ }
p.summary > a:hover { /* child */ }
p.summary + a:hover { /* next */ }
p.summary ~ a:hover { /* future */ }
p.summary a:hover,
p.summary > a:hover {
/* … */
}
h2:has(> button[aria-expanded="false"]) + div {
display: none;
}
Adding up all the simple selectors
/* 1 IDs, 2 attributes, 2 element type */
form#contact button[type='submit']:active {
border-color: seaGreen;
}
border-color
declaration comes from a selector[
1,2,2
]
1, 2, 2
v3.
10
.1
>
v3.
9
.12
3.10.1
is larger than 3.9.12
[
1
,0,2] vs [
0
,3,2] vs [
0
,2,3]
[
1
,0,2]
vs [0,3,2] vs [0,2,3]
[
0
,3,2] vs [
0
,2,3]
[0,
3
,2]
vs [0,
2
,3]
Especially “At Scale”
/* …default table styles… */
table[rules=cols i] > tfoot > tr > td,
table[rules=cols i] > tfoot > tr > th,
table[rules=all i] > tfoot > tr > td,
table[rules=all i] > tfoot > tr > th {
border-color: black;
}
[hidden] { display: none; }
display:none
on the hidden
attribute*
type
s.class
es & [attributes]
#ID
s.block .element.modifier { /* 3 */ }
.block__element--modifier { /* 1 */ }
.🤬-bootstrap {
font-weight: bold !important;
}
Building with accessible semantics from the get-go can give you expressive, meaningful style hooks for free.
form:has(:invalid) [type="submit"] { … }
.form__btn--submit--invalid { … }
demo:
(still meaningful and expressive)
@layer settings { … }
@layer tools { … }
@layer generic { … }
@layer elements { … }
@layer objects { … }
@layer components { … }
@layer overrides { … }
@layer
{ … }
@layer
<name>
{ … }
@layer reset {
audio[controls] { display: block; }
[hidden] { display: none !important; }
}
@import url(…)
layer
;
@import url(…)
layer(<name>)
;
@layer
settings
{ … }
@layer
tools
{ … }
@layer
generic
{ … }
@layer
elements
{ … }
@layer
objects
{ … }
@layer
components
{ … }
@layer
overrides
{ … }
@layer
Utilities
{ … }
@layer
Components
{ … }
@layer
Themes
{ … }
@layer
Frameworks
{ … }
@layer
Resets
{ … }
@layer reset { /* least powerful */ }
@layer default { /* … */ }
@layer theme { /* … */ }
@layer components { /* more powerful */ }
/* unlayered styles: most powerful */
@layer
<name>
,
<name>
,
<etc>
;
Only compared inside a layer
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;
}
!important
->
Layers ->
Importance
@layer reset {
[hidden] { display: none !important; }
}
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>
!important
’
(layers lower importance by default)
*the styles will be hidden from older browsers
#example {
/* ID specificity */ }
[id="example"] {
/* attribute specificity */ }
.example {
/* [0,1,0] */ }
.example.example.example {
/* [0,3,0] - same elements */ }
:is()
and :where()
Select The Same Elements
The union of both inside & outside subjects
/* (a) AND ALSO (nav .active) */
a:where(nav .active) { color: black; }
a:is(nav .active) { color: black; }
/* (h1 a) AND ALSO (main > *) */
h1 a:where(main > *) { color: black; }
h1 a:is(main > *) { color: black; }
:is()
and :where()
Also Group Selectors
h1 a:hover, h1 a:focus,
h2 a:hover, h2 a:focus,
h3 a:hover, h3 a:focus,
h4 a:hover, h4 a:focus,
h5 a:hover, h5 a:focus {
text-decoration: underline;
}
:is(h1, h2, h3, h4, h5) a:where(:hover, :focus) {
text-decoration: underline;
}
table[rules=cols i] > tfoot > tr > td,
table[rules=cols i] > tfoot > tr > th,
table[rules=all i] > tfoot > tr > td,
table[rules=all i] > tfoot > tr > th {
/* … */
}
table:where(
[rules=cols i], [rules=cols i]
) > tfoot > tr > :is(th, td) { /* … */ }
:is()
and :where()
Can Change Specificity
:where()
Removes Internal 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()
Keeps
Highest Internal 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) { /* … */ }
Also relies on :is()
selector
header {
h1 { /* header h1 */ }
> .title { /* header > .title */ }
+ p { /* header + p */ }
}
header {
h1 { /* header h1 */ }
> .title { /* header > .title */ }
+ p { /* header + p */ }
body & { /* body header */ }
}
.card {
@media (width > 20em) {
display: flex;
}
}
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()
and :where()
Are Forgiving Selector Lists
But Not Nesting
:not()
and :has()
:is()
& :not()
& :has()
Use Same Specificity
:not()
For Excluding Elements
(inside matches are removed from outside matches)
/* (p) UNLESS (.warning) */
p:not(.warning) { /* … */ }
:has()
For Selecting Context
(we can move the subject!)
form:has(:focus) { /* form with focus */ }
button:has(svg) { /* button with icon */ }
:has()
Selectorform:focus-within { /* form with focus */ }
.card:has(> figure:first-child) { /* image card */ }
.card:not(:has(img)) { /* card without image */ }
input:has(+ .error) { /* input followed by error */ }
article:has(h1, .title) a {
color: red;
}
article h1 a {
color: green;
}
Rolling out this year!
(across large teams & projects)
(through lower boundaries & proximity)
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
@scope
(
<root>
) { … }
@scope (.light-theme) {
a { /* similar to simple nesting… */ }
}
@scope (.dark-theme) {
a { /* but the _closer_ scope root wins… */ }
}
.title { /* global */ }
.post .title { /* nested */ }
.post__title { /* BEM */ }
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>
Always child or descendant
button {
background: red;
background: blue;
}
button {
background: red;
background: oklch(0.5 0.2 0);
}
<img src="…" alt="…" width="1600" height="900">
<div hidden>…</div>
width
and height
on an imagehidden
attributedetails left to the browser…
for each property of each element
A Jedi uses the [Cascade] for knowledge and defense, never for attack.
— Yoda (almost)