/* 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>
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');

<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-imageBorder 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 eachcurrentColorborder-image which is reset-only(zero or more)
demo (debug mode):





demo:
/* initial values */
display: inline;
background: transparent;
color: CanvasText;
font-style: normal;
flex-basis: auto;
/* etc. */
display:inline, even on divs…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 bodyresource://gre-resources/Establish desired preferences


Settings / Advanced / Style sheet…about:preferenceschrome://settings/appearance


!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)types (p, ul, body, etc).classes & [attributes]#IDs (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, 2v3.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*types.classes & [attributes]#IDs.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.
— Ben Myers, Stateful, Semantic Selectors
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)