HTML & CSS From Scratch, Vol. 2: Introduction to CSS Selectors, Size Units, and Font+Text Properties

Kenneth Roraback (he/him)
13 min readMar 20, 2018

--

From my last post (Vol 1), we got a glimpse of the underlying philosophy behind CSS:

What is CSS?

CSS stands for Cascading Style Sheets:

Cascading: CSS has a series of cascading inheritance based on order and specificity. By understanding those rules, you can set up defaults and then specifically target exceptions to override those defaults.

Style: Just as HTML is all about structure, CSS is all about style. It’s how you assign sizes, colors, positioning, and even some pretty cool animations.

Sheets: CSS can be broken down into multiple style sheets that can be imported separately into a document. Or injected in <style> elements in the HTML header, itself. As such, you can partition your CSS code however makes sense for your project. If you’re working on a small project with just a couple of pages, maybe everything goes in one style sheet. If you are working on a project with multiple distinct pieces, you can break up your CSS accordingly. We’ll get into more in a future post.

Let’s Dive Deeper into CSS

CSS Syntax

A CSS file is just a set of style rules, the syntax is rather simple, if unorthodox for those who are used to JavaScript. Each set of styles is enclosed in curly braces, {}, and opens with a ‘selector’—a targeting rule—outside the braces. For example, if you want to select/target all<h1> elements, your rules would be structured like this:

h1 {
/* This is a comment in CSS. */
/* Your styles go here, between the curly braces. */
}

How do we write out styles for an element? Each style is the name of a property followed by a colon, the value for that property, and a semi-colon. For those of you who have written JavaScript code, it looks just enough like a JSON object to be sufficiently confusing and likely to cause you to make typos:

h1 {
color: red;
font-size: 28px;
}

Note that my explanation didn’t mention spaces, but the code block above has a space between the h1 and the { and between the colon and the value, and that each style property is indented. None of these spaces are required for your code to work, however. If I write the same code without whitespace, h1{color:red;font-size:28px;}, it is still valid, but it’s much harder to read—particularly if you write more than a couple of style rules. As such, it is a best practice to observe the spacing above. We’ll see more examples below.

Selecting Elements using Classes and IDs

We have now seen that if you want to target an element across your site, you can just use the element name….but what if you want to target a single element in your page? Or a set of similar elements? That is where more complex targeting comes in. The most direct way to do this is by assigning elements IDs and/or classes in your HTML that you can then reference in your CSS. An ID or a classname gets applied as an HTML attribute:

<figure>
<img id="bear-photo" src="https://www.placebear.com/300/400" alt="bear in the water" />
<figcaption>A bear in the water, courtesy of placebear.com</figcaption>
</figure>
<figure>
<img class="bear-photo" src="https://www.placebear.com/300/400" alt="bear in the water" />
<figcaption>A bear in the water, courtesy of placebear.com</figcaption>
</figure>

You can then select the element in your CSS using the classname or ID that you assigned:

#bear-photo {          /* Targeting the id w/a '#' */
width: 500px;
}
.bear-photo { /* Targeting the class w/a '.' */
width: 200px;
}

With the code above, the first image will be 500 pixels wide, and the second will be 200 pixels wide. Check out the code on codepen.io if you want to see it in action.

When should I use a class vs. an ID?

Rules for using classes and IDs are pretty simple: an ID should only ever be used once in a page (which is why it’s called an ID). A single class, however, can be applied to an unlimited number of elements. In today’s world of reusable, reconfigurable widgets (which could conceivably be used in more than one place in a single page), web developers use IDs far less than they did previously, and they lean more and more on classes.

Can I target an element based on multiple classes?

Why, yes! Yes, you can! CSS allows you to chain targeting rules together. For example, let’s say you want to find every <li> with a class of ‘kitten,’ but you also assigned that class to some paragraph tags (<p>). If you were just targeting <li> elements you would do something like:

li {   /* styles in here */   }

Likewise, if you were just targeting the class, ‘kitten,’ you might write:

.kitten {   /* styles in here */   }

To target <li> elements that also have the class, ‘kitten,’ you just smash the two selectors together:

li.kitten {   /* styles in here */   }

Or based on the fact that it’s inside another element?

A friendly reminder: if a child is directly inside another element it is called a ‘child’ of that element, and if it is nested deeper in, we call it an ‘descendent.’

There are two ways to indicate that an element is a descendent of another. The first I’ll look at is the most broad. Let’s say we have an ordered list (<ol>) and an unordered list (<ul>). Both lists have the same element type for list items (<li>), but we just want to select for the list items in the unordered list:

ul li {   /* styles in here */   }

But what if you have another level of lists inside each list element? (It might sound silly at first, but come back to me after you’ve built a faceted filter sidebar or a complex navbar with multiple levels of hierarchy, like the ‘Sections’ navigation at the Washington Post.) The selector above, ul li, will find ANY <li> that is inside a <ul>, even if it is nested several elements down the chain. To select only a direct child descendent, you need the direct child selector, >:

ul > li {   /* styles in here */   }

The above tells the browser to ONLY select list elements that are direct children of an unordered list. Your selectors can get as complex as you want, with one caveat. More than a few levels deep, and selectors can start to impact browser performance. This typically isn’t a problem when you’re writing CSS by hand, but when you’re using a preprocessor like Sass, it can become easy to nest rules a bit too much (which we’ll see later). Something like the following is completely valid—and actually was taken directly from the Washington Post navigation:

.pb-f-page-header-v2 #sections-menu-off-canvas .has-sub ul li a {
color: #5a5a5a;
}

Note: the above was almost surely written in a preprocessor like Sass, in which you can nest style rules inside one another. No one would write such a needlessly complex rule by hand.

An important takeaway from nesting: You do not need a class or an ID on every element. If you’re writing semantic HTML, you’ll often be able to select specific elements based on a parent container’s class.

What if I want to select BOTH unordered lists AND ordered lists? Or h1 and h2 headings?

This is pretty simple—just comma-separate your selectors before the opening curly brace:

ul, li {   /* styles in here */   }

You can have an unlimited number of items that are comma separated, but the same rules on overly long selector rules apply—too long, and the rules can start to slow down browser performance.

What if I want to select EVERYTHING at once?

The asterisk is your friend! It’s the wildcard in CSS:

* {
margin: 0;
}

You probably never want to do something quite so generic; but you might want to target all direct children of a particular element:

article.i-hate-margins > * {
margin: 0;
}

What if I want to target items in a list based on position?

CSS has a long list of what are called pseudo-classes, which so named because they describe an element’s state—which, unlike a true class, can change—such as its position in a list(1st, 2nd, 3rd, even number, a multiple of 3, etc.) or whether the mouse is hovering them. One way to think about pseudo-classes is as a filter on the matches you get from a regular selector—they filter for a specific state. There are a lot of pseudo-classes that you can target with CSS, but we’ll focus for the moment on position. CSS provides a lot of position pseudo-classes:

  • :first-child: This selects for an element that IS a first child. That bit is important to remember; this doesn’t find you the first child of the element you’ve targeted, but rather filters out any matches that are not the first child of another element. :first-child and :last-child used to be the only options for position-based pseudo-classes, but CSS 3 expanded the options significantly!
  • :last-child: Like :first-child, but it matches elements that are the last child of another element.
  • :first-of-type: Like :first-child, but it matches on any element that is the first of its tag type. So if you have a <div> with a lot of <p> tags and a <figure> inside it, the :first-of-type selector could match BOTH the <p> and the first <figure>.
  • :last-of-type: Same as :first-of-type, but it matches elements that are the last of their type
  • :nth-child(n): Here’s where it starts to gets interesting. Now we’re selecting for the nth child (where you insert a number in lieu of ‘n’). If I want to find ONLY the 3rd element, I would use the :nth-child(3) selector. However, let’s say I want to find all multiples of 3. I can actually write this as :nth-child(3n)! I can get even fancier and write something like :nth-child(3n+1) to select the 4th, 7th, 10th, … elements (3n+1, a.k.a numbers that have a remainder of 1 when divided by 3). NOTE: unlike in JavaScript and other programming languages, the first element has an index of 1, NOT ZERO.
  • :nth-last-child(n): This is just like :nth-child, but counting backward from the end of the list.
  • :nth-of-type(n): Just like :nth-child, but it’s only counting among elements of the same type, and ignoring all other element types as it counts
  • :nth-last-of-type(n): Like :nth-of-type but counting from the end of the list.
  • :only-child: Select for elements that have no siblings (no other elements in their parent)
  • :only-of-type: Select for elements that have no siblings of the same element type.

How to Use Pseudo-Classes

Psuedo-classes can simply be chained onto another selector to scope it to the case you care about. For example, if I want to find <div> elements that have no siblings, I might look for:

div:only-child {
border-bottom: none;
}

Let’s take consider some of these position-based pseudo-classes in the following example:

<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
<li>Item 11</li>
<li>Item 12</li>
<li>Item 13</li>
<li>Item 14</li>
<li>Item 15</li>
<li>Item 16</li>
<li>Item 17</li>
<li>Item 18</li>
<li>Item 19</li>
</ul>

Which item(s) do you think will be selected with:

  • li:last-child
  • li:first-child
  • li:first-of-type
  • li:nth-child(4)
  • li:nth-child(5n)
  • li:nth-child(5n-1)
  • li:only-child

You can check your thoughts in this codepen using the same code.

Can I exclude a selector?

Yes, you can! There’s a pseudo-class for that! It’s the :not() pseudo-class. You simply put the thing you want to exclude in the parentheses. For example, to select divs that are not only children:

div:not(:only-child) {
border-bottom: 1px solid fuchsia
}

Anchor (link) Pseudo-Classes

Anchor pseudo-classes were some of the very first pseudo-classes introduced in CSS. Most of the anchor pseudo-classes are not strictly relevant for anchor (<a>) elements anymore, but when they were introduced in the first specification for CSS, they were ONLY meant for links—the specification specifically said that there should be no effect if they were applied to other elements. As needs for interactive widgets grew, so did the specification for some of these pseudo-classes

  • :link: an unvisited link. This was a key part of usability on the web when websites were largely static. It is used far less in the current age of web apps with dynamically changing content on any given page, however. In such a dynamic product, we tend to manage the intent of the :visited pseudo-class via tracking of when content changes on a page (and trigger a change in the page when there is new content).
  • :visited: a link you’ve already clicked/visited. Similar to above, this is used far less than it once was.
  • :hover: the state when your cursor is over the element on the page.
  • :focus: the state when the element is focused by the browser; this is a state that you can typically pass through if you’re hitting the tab key in your browser—it will traverse across your page from one interactive element to another
  • :active: the state when you have clicked on an element but have not yet released the mouse

When using these pseudo-classes, they should be applied in this order, precisely—if the :visited selector is used after :active, for example, it will override the active state, and nothing will happen when you click down on an element.

Top left: visited anchor tag. Top and Bottom right: unvisited anchor tags. Bottom left: an active anchor tag (the mouse button is pressed down over the element)
Left: a focused anchor tag. Right: a hovered anchor tag.

For an example of something very close to browser defaults for these states, check out Craigslist.com. For example, its DC apartment listings. New links are blue, and visited links are purple. The active links are red—click down on one and hold your mouse button down to see this—and if you tab through links, you’ll see that they get a blue outline on ‘focus.’

Most modern websites don’t use default link behaviors (and even Craigslist overrides the default underline until you hover); they customize colors and behaviors to match their brand’s style.

Homework on Pseudo-Classes

Want to play around with link states? Check out this simple codepen and play around with styles. Then, add a second (and a 3rd!) link and use a selector to style it differently from the first link. Or go back to the DC apartment listings and play with styles directly in the browser.

Pro tip: You can manually trigger the anchor pseudo-classes in your browser for debugging! In Google Chrome, for example, it’s as simple as right-clicking the element and selecting “Inspect” from the popup menu.:

In some browsers, you may have to enable developer tools before you get the ‘Inspect’ option.

Then, in the styles panel, select the :hov option:

From there, you can toggle pseudo-classes on and off as desired. Just be sure that you have the right element selected in the HTML! If you toggle the :hover state on a parent or child, it won’t affect the other elements’ state.

Other Pseudo-Classes

There are many more pseudo-classes, particularly for <input> elements, but we haven’t studied forms yet, so I won’t go into detail on those. There are also things called pseudo-elements that were introduced in CSS3, which we’ll play with in another lesson.

How do styles cascade when there are multiple selectors and overlapping style rules?

Everyone who has written CSS has been there at some point: you write some styles in your stylesheet, only to view your page and discover that something is overriding your styles. For example, you wanted a heading to have a font-size of 16px, but it’s displaying as 24px due to some other style rule. What is the logic behind that??

CSS styles are applied firstly based on specificity, and secondly based on recency within the stylesheet(s). By this, I mean that there are many levels of specificity for selectors, and when two or more selectors are at the specificity, the latest selector will take precedence for any overlapping styles.

Specificity

CSS assigns specificity points for each type of selector, with a category for each of them, so that the result is a triple of point totals:

  • element name or pseudo-class
  • class name
  • ID

Let’s explore specificity through this great example, ordered from least specific to most specific, from the CSS specification:

*               /* a=0 b=0 c=0 */
LI /* a=0 b=0 c=1 */
UL LI /* a=0 b=0 c=2 */
UL OL+LI /* a=0 b=0 c=3 */
H1 + *[REL=up] /* a=0 b=1 c=1 */
UL OL LI.red /* a=0 b=1 c=3 */
LI.red.level /* a=0 b=2 c=1 */
#x34y /* a=1 b=0 c=0 */
#s12:not(FOO) /* a=1 b=0 c=1 */
.foo :matches(.bar, #baz)
/* Either a=1 b=1 c=0
or a=0 b=2 c=0, depending
on the element being matched. */

As you can see, the more element selectors referenced, the more specific the far right (c) category gets, while class selectors makes the middle category increase, and IDs make the left category increase. IDs always trump classes, which always trump element selectors. If two styles have the same specificity, the later one wins.

There’s one other category that trumps any degree of specificity, and that’s styles that have an !important following them. The following style will override any regular selector, as well as inline styles:

li {
color: red !important;
}

If two tags are marked as !important then specificity rules determine which one is applied. You should rarely mark styles as important. For example, in my CSS, I only ever use this tag if I am overriding styles in a UI library that applies inline styles (and I may or may not be cursing the UI library’s creators as I do so).

Lastly, there is such a thing as inline styles, which you should avoid whenever possible, because they can be hard to maintain and track. They’ll also override everything except !important styles, so it quickly gets frustrating to override them

<li style="color: red;">

Next up: ACTUAL STYLING RULES!!

We’ve gone into a lot of detail on targeting, but we have yet to cover the basics of styling in CSS!

For more on styling with CSS, see Volume 3: An intro to CSS styling, units of measure, font properties, and text properties.

--

--

Kenneth Roraback (he/him)
Kenneth Roraback (he/him)

Written by Kenneth Roraback (he/him)

UX Designer + Product Design@Teleport. Defender of usability, clear communication, & semantic HTML. More Sondheim than Lloyd-Webber. www.kennethroraback.com

No responses yet