Estelle Weyl | @estellevw | Github | Press key to advance.

Welcome back!

Go Back ➹

Structural selectors

Structural selectors













  • Target elements on the page based on their relationships to other elements in the DOM.
  • Updates dynamically if page updates.
  • Reduced need for extra markup, classes and IDs
* CSS2 / IE8

First, last, & only

First, last and only

:nth- pseudo-classes





starting from the top or bottom

:nth- pseudo-classes

Target element or elements based on argument passed to the selector




:nth-last-child(An+B [of S]) 

Structural Selectors

Structural Selectors

Before Flexbox

  • 1
  • 1
  • 2
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 4
li:only-of-type{width: 100%;}

 li:nth-of-type(2):nth-last-of-type(1){width: 50%;}

 li:nth-of-type(2):nth-last-of-type(2){width: 33.33%;}

 li:nth-of-type(4):nth-last-of-type(1){width: 25%;}

Structural Selectors

Structural Selectors

Flag with Structural Selectors

USA Flag

Simpler Flag with Structural Selectors

USA Flag

Structural Selectors Level 4

:nth-child(An+B [of S]) 
:nth-last-child(An+B [of S]) 
li:nth-child(2n of .foo)
li:nth-child(3n+1 of .bar)

Specificity is 0-1-0 for :nth-child(An+B), plus specificity of the most specific complex selector in S

Safari 9+ only

Structural Selectors Level 4

select the 3rd li with the class of foo

:nth-child(3 of /* matches #8 */

Select the 3rd li if it has the class foo /* no match */

Match every even row of the visible rows

tr:nth-child(even of :not([hidden]))
  • item 1 .foo
  • item 2
  • item 3
  • item 4
  • item 5 .foo
  • item 6
  • item 7
  • item 8 .foo
  • item 9
  • item 10
  • item 11
  • item 12 .foo
  • item 13
  • item 14
  • item 15
  • item 16 .foo
  • item 17
  • item 18
  • item 19 .foo

Structural Selectors

Structural Selectors



Selects the document root, which is <html>

  • Declare font-size on :root if using rem units
  • Style :root only if showing <head> (as in our exercise files)
  • In CSS4, define Defining Variables on root. (see Variables module)


Other Logical Combinations:
Negation, Matching and Parent

:not(), the negation pseudo-class

:not - Negation pseudo-class


Specificity: weight of (s1), not the :not.

*:not(h1)     /* 0 - 0 - 1 */
*:not(.class) /* 0 - 1 - 0 */
*:not(#myId)  /* 1 - 0 - 0 */ 

Supported everywhere since IE9

:not(s1, s2)

E:not(s1, s2)
div:not(.excludeMe, .excuseYou)

Safari Only

Reminder: if a browser doesn't recognize a pseudo-class, the entire selector block fails

"RESOLVED: Drop individual invalid selectors from selector lists in *all* selector functions that take such lists, *except* for :not()."

Most specific of (s1, .s2, #s3), in this case 1-0-0.

:not - Negation pseudo-class

Negation Selectors

:is(s1, s2) :matches(s1, s2)

RESOLVED: Rename :matches() to :is() and deprecate :matches()

E:is(s1, s2)

li:is([title], [role]) a {}
li[title] a, 
li[role] a {}

:is() doesn't get invalidated if a parameter is invalid.

:is() browser support

deprecated :matches(s1, s2)

:matches(#home, #contact) aside :matches(a:active, a:focus){}
#home aside a:active, 
#contact aside a:active,
#home aside a:focus, 
#contact aside a:focus {}

Safari only, :-moz-any & :-webkit-any for FF & Chrome

current code

:-webkit-any(article, aside) :-webkit-any(article, aside) h1 {}
:-moz-any(article, aside) :-moz-any(article, aside) h1 {}
:matches(article, aside) :matches(article, aside) h1 {}
:is(article, aside) :is(article, aside) h1 {}
article article h1,
article aside h1,
aside article h1,
aside aside h1 { }
article, aside {
  article, aside {
    h1 {
      /* sass nesting*/


a:-webkit-any(.foo, .bar, .bam) span {}
a:-moz-any(.foo, .bar, .bam) span {}
a:matches(.foo, .bar, .bam) span {}
a:is(.foo, .bar, .bam) span {}
nav a:not(:matches(.foo, .bar, .bam)),
nav a:not(:-webkit-any(.foo, .bar, .bam)),
nav a:not(:-moz-any(.foo, .bar, .bam)),
nav a:not(:is(.foo, .bar, .bam)),
nav a:not(.foo, .bar, .bam),
nav a:not(.foo):not(.bar).not(.bam) {

Future standard :is()

:-webkit-any(header, aside, footer) [contenteditable]:hover {
  color: blue;
  cursor: pointer;
:-moz-any(header, aside, footer) [contenteditable]:hover {
  color: blue;
  cursor: pointer;
:matches(header, aside, footer) [contenteditable]:hover {
  color: blue;
  cursor: pointer;
:is(header, aside, footer) [contenteditable]:hover {
  color: blue;
  cursor: pointer;

:is() == preprocessors

:is(header, aside, footer) [contenteditable]:hover { /* CSS Selectors level 4 */
    color: blue;
    cursor: pointer;
header, aside, footer { /* sass */
  [contenteditable]:hover {
    color: blue;
    cursor: pointer;
header [contenteditable]:hover { /* CSS Selectors level 2 */
    color: blue;
    cursor: pointer;
aside [contenteditable]:hover {
    color: blue;
    cursor: pointer;
footer [contenteditable]:hover {
    color: blue;
    cursor: pointer;

Specificity Rules

The specificity is the specificity of its most specific argument:

:is(ul, ol, .list) > [hidden] { ... /* 0-2-0 */
ul > [hidden], 
ol > [hidden], /* 0-1-1 */ 
.list > [hidden] { /* 0-2-0 */ 
  <li hidden>This matches</li>
  <li>This doesn't</li>


Same as :is(s1, s2), but with no specificity

Specificity sometimes makes life hard:

a:not(:hover) {
  text-decoration: none;
nav a {  /* has lower specificity */
  text-decoration: underline;

:where() adds no specificity weight

a:where(:not(:hover)) {
  text-decoration: none;
nav a {  /* has greater specificity */
  text-decoration: underline;

:has(s1, s2) Parent Selector

Parent selector

document.querySelectorAll(':has(s1, s2)');

Contains a header

document.querySelectorAll('header:has(h1, h2, h3, h4, h5, h6)');

Contains no headers

document.querySelectorAll('header:not(:has(h1, h2, h3, h4, h5, h6))');

Contains something that is not a header

document.querySelectorAll('header:has(:not(h1, h2, h3, h4, h5, h6))');

Linguistic Pseudo Classess

Language Pseudo Classes (CSS 2.1)

Matches if the element has the lang attribute


Matches if the element is in the language, as determined by document language, headers, and lang attribute on ancestors.


Language Pseudo Classes (level 4)

:lang(l1, l2) - Matches and language common to the country. Accepts comma separated values.

:lang(\*-ch, \*-be)

:dir() - Matches content that is left to right (ltr) or right to left (rtl) based on language, dir attribute, or surrounding text.


Link, location, and user action

LoVe HAte

L   :link
V   :visited

H   :hover
A   :active

F   :focus

Link Pseudo Classes

Any element with an href attribute

a:link        /* unvisited links */
a:visited     /* visited links */

CSS Selectors Level 4

a:any-link    /* a:link, a:visited */
:local-link   /* target absolute URL == document URL */
              /* :not(:local-link) == external link */


Any element with an href attribute:


:any-link is the same as :is(:link, :visited)

Supported in FF, webkit and blink


:local-link {
  text-decoration: none;
  color: inherit;
a:local-link {
  /* match links on this page or section of page (#fragment) */
a:not(:local-link) {
  /* on a different document or site */

User Action Pseudo Classes


Note: always style :focus when you style :hover


You can toggle :hover, :active, :focus, and :focus-within in DevTools

:hover, :active, :focus


Never, ever, ever do....

*:focus { outline: none; }

elements that can be active


Improving accessibility:

  • If user enabled, :focus-visible matches active elements
  • Any element which supports keyboard input matches :focus-visible when focused, even if interaction doesn’t affect :focus.
  • When mousing, does not match if focus is moved to a new element which does not support user input
  • If scripts move away from a :focus-visible spot, the newly focused element should match :focus-visible.
  • If scripts move away from a NON :focus-visible spot, the newly focused element should NOT match :focus-visible either.
  • :focus-visible is applied to :focus as well.



Other Pseudo Classes



<div id="anchor">ipsum lorem....

div:target::first-line {
  font-weight: bold;

:target pseudo-class

:target example

:target presentation

:target example



The :target-within pseudo-class applies to elements for which the :target pseudo class applies as well as to an element whose descendant in the flat tree (including non-element nodes, such as text nodes) matches the conditions for matching :target-within.



Matches elements that are a reference point for selectors to match against.

/* Selects a scoped element */
:scope {
  background-color: blue;

In CSS, :scope is the :root, since we don't have scoped CSS yet.

In JS, :scope matches the element returned by querySelector(), querySelectorAll(), matches(), or el.closest()

Grid-structural selectors

Column combinator

E || F
col.selected || td {
  /* matches all cells within the column's scope*/

Time dimensional


Video & Audio




There are lots of Developer Tool featues related to selectors, including pseudo classes.

  • It's possible to toggle :hover, :active, :visited, :focus, and :focus-within.
  • Open the Washington Post to follow along with these feature explanations:
    1. Inspect an element
    2. Inspect the selectors impacting that element (in rules and computed)
    3. See how many elements are being impacted by that selector
    4. Create a selector block


Add this HTML to the page you created in the intro

  <div data-type="H">
    <div data-value="A">♥</div>
  <div data-type="C">
    <div data-value="A">♣</div>
  <div data-type="D">
   <div data-value="A">♦</div>


Targeting without IDs or Classes

There are three playing cards, each with a face side and a back, all inside a <figure> container. For right now every card is an ace.

Create the following selector blocks in your css file:

  1. The figure
  2. The cards
  3. The back of each card (as a group)
  4. The face of each card (as a group)
  5. The first card
  6. The second card
  7. The last card
  8. The face of each of those cards, individually (3 blocks)
  9. The hearts and diamonds and the clubs and spades (2 blocks)
  10. Any card being hovered

Intentionally left blank


The figure

figure {}

all the cards

[data-type] {}

backs of the cards

[data-type] > :first-child

card faces


first, second, and last card

[data-type]:first-of-type {}
[data-type]:nth-of-type(2) {}
[data-type]:last-of-type {}

faces of first, second and last cards

[data-type]:first-of-type [data-value]{}
[data-type]:nth-of-type(2) [data-value] {}
[data-type]:last-of-type [data-value] {}

red cards

[data-type="H" i],
[data-type="D" i] {}

black cards

[data-type="C" i],
[data-type="S" i] {}

hovered card

[data-type]:hover {}


Now that we know how to target cards without classes, let's add some classes to make our code easier to discuss. You can now add the card, back, and face classes to your HTML.

  <div class="card" data-type="H">
    <div class="back"></div>
    <div class="face" data-value="A">♥</div>
  <div class="card" data-type="C">
    <div class="back"></div>
    <div class="face" data-value="A">♣</div>
  <div class="card" data-type="D">
    <div class="back"></div>
   <div class="face" data-value="A">♦</div>

Your original selectors still all work.


More selectors...

Pseudo elements ➹