DevPath · Learn to code ESPTEN

CSS: selectors, cascade and specificity

Advanced selectors

Beyond the tag, the class and the id

Basic selectors (p, .notice, #header) get you out of trouble, but they fall short the moment the design gets demanding: "style the first menu item", "only the active link", "the even rows of the table". That's what CSS's more expressive selectors are for: targeting an element by its position, its state or its attributes, without touching a single line of HTML. And the best part: you see every change instantly on screen.

Combinators: relationships between elements

A combinator relates two selectors according to the position of the elements in the document tree.

/* Descendant (space): any <a> inside a .nav, at any level */
.nav a { color: teal; }

/* Direct child (>): only the <li> that are immediate children of .menu */
.menu > li { font-weight: bold; }

/* Adjacent sibling (+): the <p> that comes RIGHT after an <h2> */
h2 + p { margin-top: 0; }

/* General sibling (~): ALL the <p> siblings that follow an <h2> */
h2 ~ p { color: #555; }

The trick to never mixing up > and a space: the space means "somewhere in there", > means "the child itself, no grandchildren".

Pseudo-classes: selecting by state or position

A pseudo-class (a single :) targets an element according to a dynamic state or its position among siblings.

a:hover         { text-decoration: underline; }  /* on mouse hover */
input:focus     { outline: 2px solid royalblue; } /* when focused */
li:first-child  { font-weight: bold; }            /* the first child */
li:last-child   { border-bottom: none; }          /* the last child */
li:nth-child(2) { background: #eef; }              /* the 2nd child */
li:nth-child(odd) { background: #f7f7f7; }         /* odd ones: 1, 3, 5... */
p:not(.featured) { opacity: 0.7; }                 /* all except .featured */

:nth-child() accepts a number, the keywords odd/even, or an an+b formula (for example 3n for one out of every three). It's your best friend for those alternating-row tables, without tagging a single class by hand.

Pseudo-elements: styling generated parts

A pseudo-element (double ::) styles a specific part of the element or generates content. ::before and ::after insert content and require the content property.

.price::before { content: "€ "; color: green; }
.external-link::after { content: " ↗"; }
p::first-line { font-weight: bold; }

Without content, the ::before/::after pseudo-elements are not shown. This is the number-one slip-up: the rule looks correct, but nothing appears on screen because content is missing. Remember it and you'll save yourself a good chunk of head-scratching.

Attribute selectors

They target elements according to the presence or value of an HTML attribute.

input[type="text"]   { border: 1px solid #ccc; }  /* exact value */
a[target]            { font-weight: bold; }        /* has the attribute */
a[href^="https"]     { color: green; }             /* starts with */
img[src$=".png"]     { border: 1px solid #ddd; }   /* ends with */
a[href*="example"]   { color: orange; }            /* contains */

By combining these selectors you can target almost any element with sniper-like precision, without cluttering the HTML with filler classes. Fewer invented classes, cleaner CSS.

Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
The cascade and inheritance →