Sample Video Frame
03: Rule Sets
div > a
selector only works on the "first child" when I should have said "immediate descendents". The second mistake is I claim that div > a
has higher specificity than div a
but it doesn't, it's simply order dependent. If you reorder those lines you should get the same effect. The important part of the video is that div > a
solves situations where div a
is too aggressive and applies to all a
tags when you only want the immediate children.Now I start to show you the most basic element of CSS and how it interacts with the HTML Document Object Model (DOM). When we talked about the DOM in the HTML module I showed you how the HTML tags are structured into a tree, with parents and children. The problem with trees is they're difficult to process without some kind of "rules engine". A rules engine is a way to specify what should happen when certain things are encountered in a tree. You give the browser a set of rules, the browser "walks" the DOM, and if any part of the DOM matches your rule, then the browser runs it.
Your Files Are a Tree
A simpler way to explain trees is to use what you know about the command line from the First Steps module. If you remember you can use a command like this:
cd Projects/ljsthw/ex1
This command does a "change directory" to the Projects
, then ljsthw
, then ex1
directory. We call this a "path", and you can use that path to specify an exact location for commands. For example, you could copy a file there without using cd
first:
cp mycode.js Projects/ljsthw/ex1/
I can also write this path out as a tree, and even include any other directories. Imagine I had this structure:
- Projects
- ljsthw
- ex1
- ex2
- buttons.computer
- src
- docs
- ljsthw
If I want to access ex2
of my ljsthw
project I use Projects/ljsthw/ex2
. If I want to access the docs
of the buttons.computer
project, I do Projects/ButtonsComputer/docs
. We can call this process "flattening the tree" or "building a path".
Rules for File Paths
What if you have a huge directory structure and you want to do a command repeatedly to only certain files? Imagine in your Projects
directory you had thousands of files ending in .txt
and you needed to list them all? Are you seriously going to write out every path to every file, or even to every subdirectory? No way. You want to write one command that processes all files ending in .txt
without you telling it to go into every directory.
The first thing you'd use is the "regex" that says "all the .txt files". You then combine this with the find
command like so:
find . -name "*.txt" -print
The format for the find
comand is:
find
.
the start of the search (. means "the current directory")-name
to say you want to select files by their names"*.txt"
the regex pattern for "anything ending in .txt"-print
whatfind
should do when a file matches your pattern
The find
command takes a sequence of "selectors" and traverse your file tree. When any file or directory matches your selector it runs a command, in this case it prints it out. You can get even more complicated with find:
find . -name "ex*" -a -type d -print
This will find every directory beginning with "ex". The -a
tells find
to use "and" logic, -type d
means a directory type of file, so the selector -name "ex*" -a -type d
means "every file named ex* and type directory".
Paths in CSS
Imagine I decided to do something weird and translate a simple HTML document into a set of files and directories like this:
mkdir html
cd html
mkdir body
cd body
mkdir p
cd p
pwd
echo "Hello world" > text.txt
cat text.txt
Try this and you'll see its structure is this:
- html
- body
- p
- text.txt with "Hello world"
- p
- body
I can translate that set of directories into this HTML:
<html>
<body>
<p>Hellow world</p>
</body>
</html>
If I can use the find
command on the "HTML as directories", then how do I do the same thing to the actual HTML? I use CSS selectors (or XPath, but that's a more complicated story). Imagine I want to find all of the directories named "p" using find
:
find -name "p" -a -type d -print
In CSS I do the same thing with this:
p {
/* do stuff */
}
That first line where you see the p
is the selector, and it tells CSS that any tag with the name "p" should get this rule applied. I only have a comment there for now, but this is similar to the find
command above. CSS assumes I mean tags and names, so I only have to give the p
tag name in the selector and I'm done.
CSS can also do a complete path to a tag. Looking at our "HTML as directories", I could list all the files in the "p tag" like this:
ls html/body/p
In CSS I can do the same thing like this:
html body p {
/* do stuff */
}
I don't have to give a /
(slash) character since CSS assumes I mean a path of DOM tag elements.
Full Rule Set Format
Now that you know how CSS traverses "paths" in your DOM I can describe the full format for CSS. Here's a simple example that only changes the p
tags to blue:
p {
color: blue;
}
Breaking this down we can show the full syntax before explaining what each part does:
- You see the
p
selector, and this can get very complex, but you'll focus on using this style with a few modifications as you advance. {
starts the rule set definition and must be matched with the}
at the end.color
defines what property of the element's display you want to set. These are similar to the attributes in HTML, but CSS has mostly new names for the same things (because the CSS designers hated HTML).:
the colon sets the end of the property and the start of the value. It's similar to the=
in HTML attributes such ascolor=""
.blue
is the value you want for this property, and it's similar to the part of HTML attributes that is in the quotes as incolor="blue"
.;
ends this property. You can then start a new one on the next line.}
ends the rule set.
The format for CSS is not that complicated, and it's similar to a syntax in JavaScript. What makes CSS complicated is all of the varying properties you can set and how they're processed.
Selectors
You'll receive plenty of practice in using selectors as this module continues, but I should give you a small list of the most common selectors you'll need:
tag
-- This is what you've already seen when you just use a basic tag name likep
orh1
..class
-- This selects any tag that has this class attribute. If you have<h1 class="title">
then the selector for that is.title
.#id
-- Same as class but for id attributes. If you have<p id="main-text">
then the selector is#main-text
.tag.class
ortag#id
-- You can combine the tag, class, and id selectors to make them even more specific.parent child
-- This is what we did at the beginning of the lesson and says "any tag named child inside parent".tag1,tag2
-- Applies the rule to all tag1 or tag2 elements.div + p
-- Applies the rule to anyp
tag that is after adiv
tag.[attribute="something"]
-- Applies the rule to anything with this attribute set to "something".
This is definitely not an exhaustive list of selectors, and many of these can be combined to make them more specific. I recommend you keep things simple with only tag selectors and a little bit of class or id. Then add others as needed.
Properties and Value
Once CSS has figured out what part of your HTML this rule applies to it then must know how to change the display of the HTML. The properties and values do that. This is a list of the most common properties you'll use in CSS.
color
-- changes the font color.background
-- configures the background.border
-- configures the border of an element.display
-- changes how the element is placed with other elements.opacity
-- makes it more or less transparent.font
-- determines the font style, face, and other typography aspects.margin
-- the space between elements' boxes.padding
-- the space inside elements' boxes.position
-- how the element is positioned relative to others.width
-- the horizontal width of the element in various measurements.height
-- the vertical height of the element.
Many of these have additional more specific variants. Such as font
has font-size
, font-family
, and more. You should review the MDN documentation on CSS properties to find more.
Specificity
What happens if I have two competing rules that want to change the same property? To make this discussion clear I will use this HTML:
<span>
<h1 class="red-title">I Am Title</h1>
</span>
Nothing fancy, just two tags. Next, let's create some CSS with conflicting selectors and the same properties:
h1.red-title {
color: red;
}
span h1 {
color: blue;
}
Take some time to get this all working in an HTML file using a <style></style>
tag based on this clue:
<html>
<head>
<style>
</style>
</head>
<body>
</body>
Once you get the CSS loaded you'll see that the h1
tag is red, even though you said span h1
should turn it blue
after. Both CSS rules want to set the color
property, so the browser has to determine which one is the winner. It does this using something called "specificity" which I touched on in lesson 1. Specificity says that the class
rule h1.red-title
beats the tag rule h1
.
The simplest way to remember the specificity rules is "tag < class < id". If I say this out in English it's "tag is less priority than class which is less priority than id". This means that tags are even lower priority than id. Another way to put this is, "id beats class which beats tag".
The !important Exception
There are more to the rules, but most of the exceptions are either very rare, or simply override this hierarchy. You can add a specifier to a rule called !important
which makes that rule win against anything else:
span h1 {
color: blue !important;
}
If you add this to the example then the title will be blue. This is a good debugging tool to see if maybe it's just specificity that is causing a rule to fail rather than how you wrote it. If you add !important
and the rule works then you have some other rule with higher importance blocking this rule. If you add !important
and the rule is still broken then it's not specificity and it's actually how you wrote the rule. Check things like spelling or the selector.
p h1
with a p
tag containing an h1
tag. Apparenlty, according to MDN this actually doesn't work because browsers--for some insane reason--will pull the h1
tag out of the p
tag and then invent an entirely new structure. Why? Because it's in the standard. Why? I just told you, it's in the standard. Are you daring to question the all mighty standard?! This is the web my friends. Perfectly logical things like one tag inside another will have a random footgun exception for no other reason than someone in 1996 couldn't make it work on a NeXT cube so they "standardized" it rather than clean it up.The style="" Exception
Another option for debugging your CSS is to use the style=""
attribute on the tag like this:
<span>
<h1 style="color: blue">I am Blue</h1>
</span>
This wins all the time, so like !important
you can use it to see if your CSS rule is written correctly, or if there's something else that's blocking in in your CSS.
Specificity Hell
If you look at the HTML from Bootstrap you'll see this style:
<div id="carouselExampleSlidesOnly" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img class="d-block w-100" src="..." alt="First slide">
</div>
<div class="carousel-item">
<img class="d-block w-100" src="..." alt="Second slide">
</div>
<div class="carousel-item">
<img class="d-block w-100" src="..." alt="Third slide">
</div>
</div>
</div>
In this example from Bootstrap's Carousel you can see that it relies entirely <div class="">
for everything. Is this necessary? No, not at all. Here's my replica of that same example:
<carousel>
<figure class="active">
<img alt="Stock photo" src="…"> <figcaption>Image 1</figcaption>
</figure>
<figure>
<img alt="Stock photo" src="…"> <figcaption>Image 2</figcaption>
</figure>
<figure>
<img alt="Stock photo" src="…"> <figcaption>Image 3</figcaption>
</figure>
<prev></prev>
<next></next>
</carousel>
You can do everything Bootstrap does with simple simple custom HTML tags, so it's not necessary for to rely on classes. Bootstrap isn't the only project to do this either. Nearly every CSS framework uses this div.class
style, but they seem to not know why. They just do it because other people did it, but there is an actual reason as explained by Jeffrey Zeldman in his book, Designing with Web Standards:
...in most browsers, without CSS, the
<h1>
heading would be big and bold with vertial margins (whitespace) above and below.CSS lets you change that. With CSS,
<h1>
can be small, italic, and margin-free if it suits the designer to make it so. Alas, not in Netscape 4, which adds its default legacy renderings to any CSS rule the designer specifies. If the CSS says there shall be no whitespace below the headling, Netscape 4 will go ahead and stick whitespace down there anyway.When designers applied CSS to standard HTML markup, they quickly discovered that IE4 mainly did what they asked it to do, wheras Netscape 4 made a mess of their layouts.
Some designers abandoned CSS. Others (sadly including me) initially worked around the problem by eschewing structural markup, using constructions like
<div class="headline1">
instead of<h1>
. This solved the display problem at the expense of document structure and semantics, thereby placing short-term gain ahead of long-term viability and leading to numerous problems down the road. Said problems have now come to roost.
This means everyone uses div.class
style because Netscape Navigator version 4 didn't let you change the browser stylesheet. Netscape 4 was released in...1997 and it hasn't been used since at least 2004, but it fixed this problem in at least 2000. That means people have been using this style of CSS to support a browser that died 21 years ago. There is then no reason to use div.class
and in fact, if you're forced to use this style then the browser is not following the standards.
The Impact of div.class
The first problem with the div..class
style is readability. Take this snippet from the bootstrap example to see what I mean:
<div class="carousel-item">
<img class="d-block w-100" src="..." alt="Third slide">
</div>
This could be rewritten to simply be:
<carousel-item>
<img src="..." alt="Third slide">
</carousel-item>
Which also solves the second problem of class specificity hacking
. Because Bootstrap uses .class
everywhere it completely dominates your own CSS and makes determining the specificity of any CSS far more difficult. If Bootstrap simply used a set of predefined tags your own CSS would override their style without any need to hack specificity with !important
or resort to using id
.
Keeping it Simple
The solution to specificity hell is to simply reject the <div class="tagname">
style and get back to the original intent of CSS specificity:
- Use mostly tags in your CSS with full (or mostly complete) DOM paths for the selectors.
- Use
.class
for different variants or states of those tags. - Use
.id
for one time localized changes for a single view.
This model is not only actually semantic but you almost never need to calculate specificity. The CSS applies to tags and exact structures, classes then create variations on those rules, and ids are used in your local HTML and CSS for page specific clarifications. Doing this has removed about 90% of my specificity problems and results in cleaner CSS and HTML that's easy to maintain.
Register for Learn JavaScript the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.