An intro to CSS
Cascading Style Sheets
CSS is the standard language/format for styling web pages, which specifies what the page’s HTML will look like in the browser.
-
CSS | MDN
MDN, as is custom. -
Basics of CSS
Another ASMR introduction from Laurel. -
Google’s web.dev CSS course
Different order from ours, but pretty good. -
HTML Color Codes
Nice tools for color. -
Wakamai Fondue
“What can my font do?”
In our ongoing analogy, CSS is the skin of the web. Just like HTML, at its most basic it is still just text, in a file, on a computer. It can live inside HTML documents themselves, but is more commonly seen on its own with the extension .css
CSS came after HTML, first proposed by Håkon Wium Lie in 1994—who was working with our friend Tim Berners-Lee at CERN and wanted more control over the presentation of web pages. (Tim was against the idea, thinking it should be up to each user.) It’s had three major revisions that have grown the vocabulary:
- CSS 1, 1996
- CSS 2, 1998
- CSS 3, 1999
For the past decade or so, features have been added incrementally by browsers “within” the CSS 3 “standard”. That’s how it goes, these days.
The change in relationship between generator and consumer of information is going to take some getting used to. [...] That said, I'll comment that style sheets constitute a wormhole into unspeakable universes. People start thinking they'll just set up a little file in SGML or something else, and soon it grows uncontrollable.
Where CSS lives
Before we get into CSS itself, let’s talk about how it is incorporated with HTML. There are three ways it can be added:
- Inline on HTML tags themselves
- Via
<style>
elements in HTML documents - As separate/external files via
<link>
elements
1. Inline with style=
This is the original and most straightforward way to add styles, directly as attributes in HTML tags:
<p style="color: red;">This text will be red!</p>
Seams obvious. However this has some downsides—imagine you want to style all of your paragraphs in the same way, and with multiple properties:
<p style="color: red; font-family: sans-serif;">This text will be red!</p>
<p style="color: red; font-family: sans-serif;">I’d also like this to be red.</p>
<p style="color: red; font-family: sans-serif;">And they are all sans-serif, too.</p>
It makes it hard to read, and hard to change and maintain—you’d have to update every single instance. (In software, we’d refer to this as brittle—meaning it is easy to break.)
2. Along comes <style>
So the next way that was added to the standard was using a special HTML element, <style>
, that wraps blocks of CSS that then apply to an entire document. They go up in the <head>
of our HTML documents:
<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
<style>
p {
color: red;
font-family: sans-serif;
}
</style>
</head>
<body>
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
<p>This is third paragraph.</p>
</body>
</html>
The rules are written written with selectors—more on those, below. But importantly, we can now control the color of all the paragraphs easily, at once.
3. External with <link>
So this is already much better, allowing us to style whole pages easily and consistently. But what about when we have multiple pages? If you wanted a whole site to use the same styles, you’d have to duplicate the <style>
tag over and over, updated it everywhere whenever you changes. Still brittle. So along comes the <link>
element:
<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
<p>This is third paragraph.</p>
</body>
</html>
And then in a separate style.css
file (in this case, in the same directory as our HTML file), we can have the same rules as before—no need for the outside wrapping <style>
tag:
p {
color: red;
font-family: sans-serif;
}
This will apply to any page that we add the <link>
to, and updating the styles will now change the color of the paragraphs in our entire web site.
We’ll talk more about specificity later, but it is worth noting that the inline approach will usually take precedent over other methods—under the “closest, then lowest” logic.
CSS rules
Even though it is used to style HTML elements, the syntax of CSS is very different. CSS rules are made up of selectors, used to target certain elements, and then the declarations that you want to apply to them:
The curly brackets {
}
(also known as mustaches or handlebars, for their shape) enclose all the declarations you want to apply to a given selector. These declarations are in turn made up of properties and values.
Properties are always separated from their corresponding values by a colon :
, and each declaration line has to end in a semicolon ;
. Also, there are no spaces between values and their units (like 20px
)!
There are many, many CSS properties. (Here is a shorter “common” list.) We’ll go through some in our exercise, but look through these to become more familiar.
Ergonomics
Just like HTML, CSS does not care about capitalization, extra white space, or line breaks. People generally use tabs/indenting to indicate hierarchy, but again it is just whatever makes it easier for you!
p {
color: red;
}
/* Is the same as… */
p { color: red; }
I generally “single-line” rules when there is only one property declared, as I find it easier to read.
Capitalization does matter when using IDs or classes as selectors, which have to match the HTML to target correctly. Like with HTML, it’s easiest just to be consistent and stick to lowercase.
Basic selectors
Selectors are used to target certain HTML elements within the page. These can get pretty complicated, but we’ll look at the three simplest and most common methods to start:
- Elements
- Classes
- IDs
1. By element type
If you want to change the styles for all instances of a given HTML element, you drop the <
>
from the tag for an element selector. These are called type selectors:
/* Click `index.html` to see the HTML. */
p { color: red; }
blockquote { color: blue; }
footer { background-color: lightgray; }
/* This is a “wildcard,” targeting any element. */
* { color: gray; }
<!DOCTYPE html>
<html>
<body>
<h1>This heading will be gray</h1>
<p>This paragraph will be red.</p>
<blockquote>This blockquote will be blue.</blockquote>
<p>This paragraph will also be red.</p>
<footer>
<blockquote>This blockquote, also blue, nested in a gray footer.</blockquote>
<a href="#">A gray link, from the wildcard</a>
</footer>
</body>
</html>
Note that CSS has different /* comment syntax */
, too.
2. With a class
But maybe you don’t want to style all of the paragraphs. You can then use a class
to target specific instances. They are added as an attribute on the element you want to target:
.highlight {
color: blue;
}
.faded {
opacity: 33%;
}
<!-- Click `style.css` to see the CSS. -->
<!DOCTYPE html>
<html>
<head>
<title>Class</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h1 class="highlight">This heading will be blue</h1>
<p>This paragraph will remain black, since it has no class on it.</p>
<p>So will this one.</p>
<p class="faded">This paragraph will be faded.</p>
<p class="highlight faded">This paragraph will be blue and faded, with both classes.</p>
</body>
</html>
The value here is our class name, which we write in CSS by prefixing with a .
as with .highlight
and .faded
.
You can use these over and over, on any kind of element. And individual elements can have multiple classes, too. (We’ll talk about how conflicting rules are handled, below.) Class names can be whatever you want—there are whole methodologies about what to call these things. They are the most common way to target things in CSS.
3. With an ID
You can also use an id
, which is a kind of special attribute that can only be used once in an HTML document. These are useful thus useful for targeting singular things—like your navigation, the document title, specific headings, etc:
#title {
color: red;
}
#introduction {
color: blue;
}
<!DOCTYPE html>
<html>
<head>
<title>ID</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h1 id="title">This heading will be red</h1>
<p>This paragraph will be black, with no ID.</p>
<p id="introduction">This paragraph will be blue.</p>
</body>
</html>
These are prefixed by #
in CSS, as with #title
and #introduction
. They can also be used as link destinations.
Fancy selectors
Combinations and groups
You can use combinations of the above elements, classes, and IDs to be even more specific—however, this likely means you just need to rethink your HTML structure. (We’ll unpack specificity, below.) More commonly, you might apply declarations to multiple selectors, called group selectors, with a comma-delineated selector list:
/* Paragraphs with the class `warning`. */
p.warning { color: red; }
/* The `h2` isn’t really necessary here… */
/* Since the `id` is already unique! */
h2#second-heading { color: blue; }
/* This applies to both selectors! D.R.Y. */
blockquote,
footer {
background-color: lightgray;
}
<!DOCTYPE html>
<html>
<head>
<title>Combinations and groups</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h2 id="first-heading">This heading will be black</h2>
<p class="warning">This paragraph with be red.</p>
<blockquote class="warning">This blockquote will black, despite having the same class.</blockquote>
<h2 id="second-heading">This heading will be blue</h2>
<footer>
<p>Just another paragraph, nested in a footer.</p>
</footer>
</body>
</html>
With specific attributes
You can use the various attributes as selectors, too. These are usually very similar to using classes, but can help you differentiate things like internal and external links, for example:
/* Links that start with `https://` are external. */
a[href^='https://'] { color: teal; }
/* Links with `.pdf` anywhere in them. */
a[href*='.pdf'] { color: green; }
/* The `open` attribute is added when you toggle. */
details[open] { color: gray; }
<!DOCTYPE html>
<html>
<head>
<title>Attribute</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>
You might use these to differentiate
<a href="/">an internal link</a>
from an
<a href="https://newschool.edu">an external one</a>,
or ones for
<a href="/assets/PUCD_2035_F_FEHRENBACH_F22.pdf">PDF files</a>.
</p>
<details>
<summary>Or an open/close state for details/summary</summary>
<p>This won’t be visible to start, but should be teal when you can see it.</p>
</details>
</body>
</html>
Pseudo-classes
These are special selectors, added to element
, class
, or id
which target unique states or instances of HTML elements. You’ll often see these used to target link states:
/* A link that hasn’t been visited. */
a:link { color: red; }
/* After you’ve visited the link. */
a:visited { color: purple; }
/* Change the color when the mouse is over it. */
a:hover { color: fuchsia; }
/* And when the mouse is clicked. */
a:active { color: maroon; }
<!DOCTYPE html>
<html>
<head>
<title>Pseudo-classes</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>This is a paragraph with <a href="#">a link</a> in it.</p>
<p>And another one with <a href="/topic/css">another link</a>.</p>
</body>
</html>
:hover
also works on any element, not just links!
Other common examples have to do with counts and positions:
/* The paragraph is the first descendent of its parent. */
p:first-child { color: blue; }
/* Paragraphs that are the 4th children. */
p:nth-child(4) { color: purple; }
/* Last one. */
p:last-child { color: fuchsia; }
/* Only one. */
p:only-child { color: red; }
/* You can invert these, too. */
div:not(:first-child) { background-color: lightgray; }
<!DOCTYPE html>
<html>
<head>
<title>More pseudo-classes</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<div>
<p>This should be blue.</p>
<p>Standard black, here.</p>
<p>Also black.</p>
<p>This one is purple!</p>
<p>Back to black.</p>
<p>This will be fuchsia.</p>
</div>
<div>
<p>This should be red.</p>
</div>
</body>
</html>
Pseudo-elements
Slightly different the various pseudo-elements, which let you style a particular part of an element. You’ll most often see these as :before
and :after
, which let us insert things around text.
/* These are inserted before and after the heading. */
h1:before,
h1:after {
color: red;
content: ' ••• ';
}
p:first-letter {
color: red;
font-size: larger;
font-weight: bold;
}
p:first-line { color: maroon; }
/* After external links. */
a[href^='https://']:after { content: ' ↗'; }
<!DOCTYPE html>
<html>
<head>
<title>More pseudo-classes</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h1>Pseudo-elements are clever</h1>
<p>The first letter of this paragraph will be red, and the first line maroon, no matter where it wraps or how long it ends up being.</p>
<blockquote>
You can use these to <a href="https://newschool.edu">embellish links</a>
</blockquote>
</body>
</html>
Finally, combinators
Last, you will often want to target something based on its relationship to other elements—its siblings or its parents. For this, CSS has combinators, which let you relate all the various selectors we’ve learned about here together.
/* The “descendant” combinator. */
/* Only paragraphs inside the header. */
header p { color: blue; }
/* The “child” combinator. */
/* Only immediate children paragraphs of `body`. */
body > p { color: gray; }
/* “Adjacent sibling” combinator. */
/* Only immediately preceded by another paragraph. */
p + p { color: teal; }
/* “General sibling” combinator. */
/* Only paragraphs following `h2`. */
h2 ~ p { color: green; }
<!DOCTYPE html>
<html>
<head>
<title>Combinators</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<header>
<h1>Combinators are handy</h1>
<p>This paragraph should be <em>blue</em>, since it is inside the header.</p>
</header>
<p>This paragraph should be <em>gray</em>, as an immediate child of <strong>body</strong>.</p>
<p>This one should be <em>teal</em> though, as it follows another paragraph.</p>
<p>Same for this one.</p>
<h2>This heading doesn’t have a style itself</h2>
<p>But will make this following paragraph <em>green</em>.</p>
<p>And this one too, still after the heading.</p>
</body>
</html>
Importantly, combinators can only “see” elements before and above themselves—meaning their previous (older?) siblings or their parents. This directionality somewhat corresponds with the cascade, which we’ll talk about shortly.
Target your parents!
For many, many years people wanted a “parent selector” in CSS—meaning a way to apply a style to a parent/container based on one of its children. This was not possible before, as we mentioned above.
CSS has finally added the :has()
pseudo-class. It will allow us to write much simpler, logical styles:
div:has(p) { background-color: red; }
Safari and Chrome both just added support, so this should be safe to use in the coming months.
Specificity
The first three targeting methods (element
, .class
, #id
) are listed in increasing order of specificity, meaning that a class trumps an element rule, and an ID trumps a class. IDs are thus more specific than classes, which are more specific than element selectors. (And you shouldn’t really use them, but inline styles beat them all.) Take this example:
p { color: red; }
/* Will “win” over an element selector. */
.maroon { color: maroon; }
/* But IDs will supersede classes. */
#intro { color: blue; }
#warning { color: teal; }
<!DOCTYPE html>
<html>
<head>
<title>Specificity</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>This paragraph will be red, from the <em>element</em>.</p>
<p class="maroon">This paragraph will be maroon, from the <em>class</em>.</p>
<p>This paragraph will be red again, no <em>class</em>.</p>
<h2 class="maroon" id="intro">This heading will be blue, from the <em>ID</em>, even though it has the <em>class</em> too</h2>
<p id="warning">This paragraph will be teal, from the <em>ID</em>, beating the <em>element</em>.</p>
<p class="maroon" style="color: gray;">This paragraph will be gray, from an <em>inline style</em>, which beats everything.</p>
</body>
</html>
You could write a long book (and many people have) about CSS specificity—the myriad of ways that some CSS rules take precedent over others. It is often one the more frustrating parts (especially when working with legacy code that is poorly considered). Suffice it to say it’s complicated. The easiest way to avoid specificity problems is generally to stay at the same level throughout your HTML, usually by just using classes throughout.
The cascade
We haven’t even talked about that first C! Remember, it stands for cascading. This means that when there is a tie (like two classes applying the same property), the lowest rule wins—literally the one further down within a CSS document, or within a style tag. If you have multiple CSS documents with <link>
element, the lower linked document will take precedence.
.note { color: gray; }
/* Because this is lower, it will “win” the tie. */
.warning { color: red; }
<!DOCTYPE html>
<html>
<head>
<title>Cascade</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p class="note">This paragraph will be gray, with just the “note” class.</p>
<p class="warning note">This paragraph will be red though, with both classes.</p>
<p class="note">This paragraph will also be gray, with just “note” again.</p>
</body>
</html>
And inheritance
To add even more confusion, some CSS properties set on a parent also apply to their children—such as color
or font-family
. Most spacing/layout properties, like width
and margin
do not. (More on those, next week.)
This allows you to quickly set some properties globally, without having many brittle/redundant rules, as we did before:
body {
color: gray;
font-family: sans-serif;
}
span {
color: blue;
font-style: italic;
font-weight: bold;
}
<!DOCTYPE html>
<html>
<head>
<title>Inheritance</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h1>This is a heading</h1>
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
<p>This is <span>third</span> paragraph.</p>
</body>
</html>
All the children inherit the body
styles. Ah, finally, sans-serif
.
Color and type properties
Alright, so all this has been about targeting elements—what about actually styling them? Let’s introduce a few quick properties to get us started.
Color!
Besides the basic examples above, color can be specified in a few different ways:
/* There are a bunch of named colors. */
li:nth-child(1) { color: dodgerblue; }
/* Or you can specify a hex value. Brands love these. */
li:nth-child(2) { color: #1e90ff; }
/* Or RGB for Red, Green, Blue, if you think like a screen. */
li:nth-child(3) { color: rgb(30, 144, 255); }
/* Or Hue, Saturation, Lightness, which is more human. */
li:nth-child(4) { color: hsl(210, 100%, 56%); }
/* RGB and HSL have a fourth value, for Alpha (transparency). */
li:nth-child(5) { color: rgba(30, 144, 255, 50%); }
li:nth-child(6) { color: hsla(210, 100%, 56%, 0.5); }
/* Alpha as a percentage, 0–100%, or a decimal, 0–1. */
li:nth-child(7) {
color: dodgerblue;
opacity: 0.5; /* This will affect everything, not just text! */
}
li:nth-child(8) {
color: white;
background-color: dodgerblue; /* Same for backgrounds! */
}
<!DOCTYPE html>
<html>
<head>
<title>Color</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<ol>
<li>This paragraph will be <em>dodgerblue</em>.</li>
<li>Which can also be written as <em>#1e90ff</em>.</li>
<li>Or as <em>rgb(30, 144, 255)</em>.</li>
<li>And as <em>hsl(210, 100%, 56%)</em>.</li>
<li>This has an <em>alpha</em> (transparency) value in the color.</li>
<li>Same for this one.</li>
<li>This uses a separate <em>opacity</em> property.</li>
<li>All of these work for backgrounds, too!</li>
</ol>
<style>li:not(:first-child) { margin-top: 0.666em }</style>
</body>
</html>
There are 147 named CSS colors! yellowgreen
is a favorite!
- Chen Hui Jing, on the history of css colornames.
Named colors are quick to work with when you know a few, but it doesn't allow you to adjust and work with your own variations.
These can also all be applied to background-color
(and border
, but we’ll talk about that next week).
Fonts!
Then perhaps most importantly, you’ll always be customizing your typography. Remember, the web is text all the way down:
/* Importing a font via a url like Google/Fonts. I'm not doing it because I don't want Google to spy on my site.*/
@import url('http://notarealurl/fonts/googleapis.com/roboto');
/* These will depend on your user’s platform and browser. */
li:nth-child(1) { font-family: serif; }
li:nth-child(2) { font-family: sans-serif; }
li:nth-child(3) { font-family: monospace; }
li:nth-child(4) { font-family: cursive; }
li:nth-child(5) { font-family: fantasy; }
/* You can also choose a specific typeface. */
li:nth-child(6) { font-family: 'Futura', 'Verdana', sans-serif; }
/* But the user has to have them installed on their device. */
/* Otherwise it will “fall back” to the next/right value. */
/* The `@import` would let you use this on any computer. */
li:nth-child(7) { font-family: 'Roboto Condensed', sans-serif; }
/* You can also host fonts along with your site files. */
@font-face {
font-family: 'comic-mono';
src: url('../comic.woff') format('woff');
}
/* And then reference them, as above. */
li:nth-child(8) { font-family: 'comic-mono', serif; }
<!DOCTYPE html>
<html>
<head>
<title>Font family</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<ol>
<li>Default serif</li>
<li>Default sans-serif</li>
<li>Default monospace</li>
<li>Default cursive, you don’t see this much</li>
<li>Default fantasy, maybe don’t do this</li>
<li>Futura, on a Mac; Verdana, on a PC</li>
<li>Some condensed Roboto, imported from Google Fonts</li>
<li>This will be in Comic Sans Mono, using @font-face</li>
</ol>
<style>li:not(:first-child) { font-size: 18px; margin-top: 0.666em }</style>
</body>
</html>
Web font licensing is a big subject but there are many places that offer open-source, or free to use typefaces nicely packaged for web use. You can select families and weights there to easily include in your pages, as in the example above.
-
Fonts by Womxn
curated by Loraine Furter -
Flinttype
typefaces by (female, lesbian, inter, non-binary, trans, agender) -
Velvetyne
free and libre fonts from France
Other type properties!
Once you’ve got a font-family
in, there are additional properties to control the typography:
/* Importing a font via a url like Google/Fonts. I'm not doing it because I don't want Google to spy on my site.*/
@import url('http://notarealurl/fonts/googleapis.com/yourfont');
/* Apply this family to everything. */
body { font-family: 'Work Sans', sans-serif; }
/* Size is the first thing you’ll want to adjust. */
li:nth-child(1) { font-size: 28px; }
li:nth-child(2) { font-size: 10px; }
/* Browsers usually default to 16px. */
/* Then the space between your lines, relative to your font-size. */
li:nth-child(3) { line-height: 200%; }
/* This could also be written as “32px”, or just “2”. */
/* You can specify different weights. */
li:nth-child(4) { font-weight: 100; }
li:nth-child(5) { font-weight: 900; }
/* These go from 100–900, depending on what your family has. */
/* Default is 400, and you’ll see “bold”—which maps to 700. */
li:nth-child(6) { font-style: italic; } /* Default is “normal”. */
li:nth-child(7) { text-transform: uppercase; } /* YOU CAN YELL */
/* This uses a relative unit, based on the font-size. */
li:nth-child(8) { letter-spacing: 0.1em; }
li:nth-child(9) { letter-spacing: -0.1em; } /* Negative values. */
/* Keep in mind these don’t apply semantic meaning… */
li:nth-child(10) { text-decoration: underline; } /* Even non-links. */
li:nth-child(11) { text-decoration: line-through; }
/* Decoration should be paired with a tag, like <em> or <del>. */
li:nth-child(12) { text-align: center; } /* Default is “left”. */
li:nth-child(13) { text-align: justify; }
li:nth-child(14) { text-align: right; }
<!DOCTYPE html>
<html>
<head>
<title>Font properties</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>Everything here will fallback to <em>sans-serif</em>, inherited from the <em>body</em> rule.</p>
<ol>
<li>This will be much larger</li>
<li>And then much smaller</li>
<li>This will have a much looser <em>line-height</em> (leading), when it wraps across lines</li>
<li>This will be very light</li>
<li>And then very heavy</li>
<li>You can force italics, without an em tag</li>
<li>Or uppercase, without editing</li>
<li>This will have looser <em>letter-spacing</em> (kerning)</li>
<li>This will be tighter</li>
<li>You can underline some text</li>
<li>Or cross it out</li>
<li>And center it</li>
<li>Or you could even justify it</li>
<li>Or right-align it</li>
</ol>
<style>li:not(:first-child) { margin-top: 16px }</style>
</body>
</html>
'For now, you can just specify units in px
. But we’ll talk about other absolute and relative units soon.' height='85rem'
Resets
As we talked about last week, browsers have their own, built-in way that they display HTML elements. These user-agent styles are specific, somewhat, to each platform and each browser. This is the “look” we have been seeing when we write plain HTML without any CSS—usually Times New Roman, with blue links, and small spacing between elements.
Often, when you are working towards your own design, you will find yourself fighting against these built-in styles. Many people instead start with resets—a semi-standard collection of CSS rules that “zero out” the browser’s built-in styles. This means you have to write everything yourself, but you have more control and aren’t building on unknown foundations. And things should be (more) consistent, across browsers and platforms.
Here is a simple, modern one for your <head>
:
<link href="https://raw.githubusercontent.com/elad2412/the-new-css-reset/main/css/reset.css" rel="stylesheet">
The author of HTML documents has no influence over the presentation. Indeed, if conflicts arise the user should have the last word, but one should also allow the author to attach style hints. The last point has especially been a source of much frustration among professions that are used to be in control of paper-based publishing. This proposal tries to soften the tension between the author and the reader.
This page was adapted (ever so slightly) from Michael Fehrenbach's CSS Intro.