Explanation / demonstration of CSS selectors, and some pseudo-elements


Universal selector

Match all non-pseudo-elements with *. So, to set all non-pseudo-elements in sans serif, use * { font-family: sans-serif; }

Element selector

Match the <dt> element with the dt selector, so you can say this: dt { text-transform: uppercase; }, and get this:

Some term
An amazingly spot on definition of "some term".

class selector

Match elements with the class name .cls-bigger

multiple class selector

Match an element that is both .cls-bigger and .cls-blue with the multiple class selector .cls-bigger.cls-blue. In this case let's italicise it with .cls-bigger.cls-blue {font-style: italic; }

id selector

Use the selector #me to match an element with an id of me.


attribute selector: simple presence

Match an element assigned the attribute data-hello-world with the selector [data-hello-world]. This will match no matter what the value of the attribute, or even if the attribute doesn't have a value.

attribute selector: exact value match, case insensitive

Match those elements with the attribute data-highlight-color set to the value "blue" or e.g. "BLUE" (or any other case variation of the same string) using the selector [data-highlight-color="blue" i]

attribute selector: exact value match, implicitly case sensitive

Only match those elements with the attribute data-highlight-color set to the value "RED" but not, e.g. "red" (nor any other case variation of the same string), using the selector [data-highlight-color="RED"]:

Note that case sensitivity can be explicitly specified with the selector [data-highlight-color="RED" s] under level 4, but not sure why you'd want to.

attribute selector: word match

Make the text green if "grass" is a word (a whitespace-separated string) in the value of the data-ground-type attribute, by matching [data-ground-type~="grass"]

attribute selector: starts with string

Match fully qualified links with [href^="http"]

attribute selector: ends with string

Match links ending in ".pdf" with [href$=".pdf"]

attribute selector: substring match

Match links containing the string "untrusted" in their path with [href*="untrusted"].

Browse the safe content with confidence, but be careful of the untrusted content.

attribute selector: beginning of a hyphen-separated value list

Used mainly for the use case: matching of elements with any en value of hreflang.

Structural complex selectors

Descendant selector

Match any <span> living anywhere inside a <ul>, with ul span.

Child selector

Match any child <p> of .parent, but none nested more deeply, by using .parent > p, in this case turning it steel blue.

Adjacent sibling selector

Match a target element that immediately follows a specified sibling with immediately-preceding-sibling-selector + target-selector

Following siblings selector

Match one or more targets that follow a specified sibling with preceding-sibling-selector ~ target.

Table / grid column selector

Match a target within the specified column of a table or grid. No implementations yet in 2020, more details on MDN.

Logical pseudo-classes


:is (used to be called :matches), takes a list of elements, any one of which will cause a match. Here, elements selected with :is(span, q) are coloured blue:

Note that unlike the usual parsing rules of CSS, the selector list within is is forgiving: if a selector within is is not recognised by the browser, rather than invalidating the entire selector containing is, only the individual invalid selector within is is ignored.

is takes the specificity of the most specific selector in its arguments.

Check browser support.


:where always has a specificity of 0, but otherwise is identical to :is.


:has takes a selector list as an argument and matches the selector it's bound to when any selector in the list matches. This could easily be used to match an element based on its descendants. Before level 4 this was not possible in CSS. Unfortunately as of Jan 2021 there is no browser support yet, but you can keep an eye on it.


:not matches elements that do not match the supplied selector or selector list. Note that you can't pass pseudo-elements, they won't work with :not. Browser support is good for passing a single selector; if you want to pass a selector list (this is the level 4 addition), check browser support.

Passing a single selector

To match all but the third child in the list, use li:not( :nth-child(3) )

You can chain individual single selector :nots together to get some of the effect of a selector list, for example to match all but the first and last list items, you can use li:not(:first-child):not(:last-child)

Passing a selector list

To match all but the first and last list items, as above but using a selector list instead of multiple :not clauses, use li:not( :first-child, :last-child)

Structural pseudo-classes


:root matches the root element. Can also use <html> in html docs, but :root is necessary for other doc types e.g. XML, SVG etc.


:empty matches empty elements. N.B. element nodes and text nodes (including whitespace) prevent a containing element from being empty. Comment nodes do not prevent their parent element from being empty.

Here, empty list items have a background pattern applied.

Child-indexed pseudo-classes

Selectors level 3 describes this type of matching as being based on the index of the target element relative to its parent, as encapsulated in the inclusion of "child" in some of the pseudo-class names. Selectors level 4 allows for a non-element parent, or no parent at all, by describing an element's relative index with respect to its siblings. Language in this section refers to "child" to match the pseudo class names, but if you're in a level 4 implementation, the implied parent is not a requirement.

Note that indices are always 1-indexed.


:only-child matches if an element is the only child element of its parent.


:only-of-type matches an element of specified type that is the only child element of that type of its parent. Here we're using span:only-of-type to target <span> elements that are the only span element of their parents:


:first-child matches an element that is the first child of its parent.


:first-of-type matches an element that is the first child element of its type, (it needn't be the first overall element child). Here we're using span:first-of-type to select the first <span> element.


:last-child is the converse of :first-child.


:last-of-type is the converse of :first-of-type.

The An+B microsyntax

Some of these function-like pseudo-elements are invoked with (An+B), which can be useful to match a specific child, or all child elements at regularly spaced intervals. Child-indexed pseudo-class selectors using this microsyntax match a child element when its index is the result of the evaluation of terms inside the parentheses. You can think of the child list as being is divided into A groups (the last group also containing any remainder), and it's the Bth element of each group that is matched.

For An+B, n is a literal representing consecutive integers between 0 and the number of child elements, Arepresents a multiple of n, and B is an integer offset which may be positive, negative, or 0.

Just a few of the things you can do with this are shown as examples below:

:nth-child(An+B of S)

Counts from the beginning of the list of child elements.

S represents a user-supplied selector list used to scope the child list, think of it as a more flexible version of :nth-of-type where you're not restricted to scoping by the type of the child element. [Note that as at December 2020, only Safari supports of S, so the scoping isn't ready for prime time yet, but keep an eye on the browser support.

exactly this one, specific child

:nth-child(2) will match exactly the second child.

the first 4 children

:nth-child(-n+4) will match the first four children.

every second child

:nth-child(2n) will match every other child, starting with the second. (Note that 2n is aliased to even, so the more readable :nth-child(even) may be preferred.)

:nth-child(2n+1) will match every other child, starting with the first. (Note that 2n+1 is aliased to odd, so the more readable :nth-child(odd) may be preferred.)

every third child, starting counting from the second

:nth-child(3n+2) will match the second child, then every third one after that.


The same as :nth-child, but counting from the end rather than the beginning of the list of child elements.


As :nth-child, but only elements of the element type to match are included in the evaluation of the child count. It is more specific than :nth-child.


As :nth-last-child, but only elements of the element type to match are included in the evaluation of the child count. It is more specific than :nth-last-child.

Shadow DOM

:host, :host(selector) and :host-context(selector)

These are used to target the Shadow host from within the Shadow DOM CSS. Shouldn't really be possible as the Shadow host is outside of the Shadow root, but there are good reasons for it. Basically, Shadow hosts are weird.

:host matches the Shadow tree's Shadow host.

::host(selector) matches the Shadow tree's Shadow host if it also matches the selector.

:host-context(selector) matches a Shadow host if it, or one of its Shadow-including ancestors matches the selector (meaning that this selector pierces Shadow boundaries in its upward search). Browser support patchy (Jan 2021), so check before you use.

Dynamic pseudo-classes

Location pseudo-classes

Note that privacy concerns may cause a browser to disregard developer-supplied :link / :visited styles. In that case it should still style them differently from each other.

User action pseudo-classes

:focus and :focus-within

:focus matches the currently focused element.

:focus-within matches an element when that element is an ancestor of the currently focused element. Note that focus-within is a rare example in CSS of being able to style an ancestor depending on the state of a descendant. [Well, okay, in theory there's :has(), but no browser supports that as of Jan 2021.]


This next form uses focus-visible rather than focus. This allows the user agent to use heuristics to determine whether to apply the rules or not, based on whether it seems that the user needs to know where the focus is. One example is navigation mode: both tabbing through and clicking on text inputs and buttons will result in any focus styles being used, whereas if you specify focus-visible instead, the browser decides whether or not to show them when the element receives focus. This is a good thing as clicking on a button and seeing a big outline appear unnecessarily on it can be ugly. Check browser support.

Input pseudo-classes


Matches an interactive element that's explicitly disabled.


Matches an interactive element that's not explicitly disabled.


Matches an element that's user-alterable.


Matches an element that's not user-alterable.

I'm not alterable by the user.


Matches an element that's currently displaying its placeholder. Note the difference compared to the pseudo-element ::placeholder , which matches the placeholder text itself.

Showing a placeholder:

Not showing a placeholder:


Matches the default element in a set of radio buttons or checkboxes, the initially selected <option> element , and form's default submit button.


Matches checked checkboxes and radio buttons, and selected <option> elements.


:indeterminate matches a radio <input> element where no radio buttons are selected, and also a <progress> element that lacks a value attribute. A checkbox <input> element may match :indeterminate if its IDL :indeterminate property is set to true. Not sure in practice when that happens, but there's some details in the WHATWG spec.

Here, matching :indeterminate elements have a teal border.

Click on the progress indicator below to toggle the presence of a value attribute:

:required and :optional

Matches a form element whose value is required or optional, respectively.

:valid and :invalid

:valid matches <input> and <form> elements that validate successfully. :invalidmatches elements that don't validate successfully. Elements without validity semantics can neither be :valid nor :invalid.

:in-range and :out-of-range

Applies to form control elements with data range limits range limits.


A validation failure matching one of the above failure states, but only after the user has attempted to submit the form, and before they have interacted with it again. But also at other times too, at the discretion of the user-agent. No browser support yet as of Jan 2021.


Matches a user-input element that has no contents (not the same as :empty, which matches an element without any non-comment child nodes). Note that this is not supported in any browser and is marked as "at risk".

Time-based pseudo-classes :past, :current and :future (WebVTT)

Relate to currently displayed, or active position, in some timeline. Might be used for highlighting the currently spoken phrase from VTT, for example. No browser support information available.

Play-based pseudo-classes :playing and :paused

Note that :playing includes situations when the user intent is that the media is playing, even if it currently isn't, for example if the resource stream is buffering. :paused includes the state before playing has been activated by the user.

Linguistic pseudo-classes

:lang(language code)

matches elements of the specified language. In contrast with the lang attribute selector, this :lang pseudo element takes many document methods of language specification into account (including the lang attribute).

Make Portuguese text a different color:

:dir(language direction)

Left-to-right languages ara matched with dir(ltr), right-to-left languages are matched with dir(rtl). This is superior to [dir="ltr"] / [dir="rtl"] as the pseudo-class matching includes the browser's knowledge of document semantics. Stylistic states are not used in the matching, so the CSS direction property is irrelevant here.

Only supported in Firefox as at Jan 2021. Experience teaches this is difficult but not impossible to provide some fallback for (using the [dir] attribute selector). It can get gnarly if you're trying to cater for the possibility of nested language direction changes.


Pseudo-elements are featureless, and so aren't matched by any other selector.

As pseudo-elements don't modify the document tree, they do not affect the interpretation of structural pseudo-classes.

User action pseudo-classes are the only type of pseudo-classes that may be compounded to a pseudo-element, and then only if the definition of that user action pseudo-class permits it.

Only pseudo-elements defined to have internal structure may be followed by descendant combinators.

Typographic pseudo-elements

::first letter

::first-letter matches the first letter within an element.

"First-letter" needn't be an actual letter, it refers to the first typographical letter unit on the first formatted line, so could be a digit, for example. It also includes any preceding punctuation and interleaving space. As ::first-letter is always contained within ::first-line if it exists, ::first-letter can inherit from ::first-line.

::first line

::first-line matches the first letter within an element.

The first line of this paragraph is always going to be bold, no matter how long it is. If you resize the viewport, you will still be able to see the first line of this paragraph emboldened. That's how it works.

Only a limited subset of CSS properties are usable to style ::first-line.

Highlight pseudo-elements

Only a very few properties may be set on these. They mustn't affect layout, and must be performant.


:selection refers to content selected to be the target or object of a future user interaction.


::target-textrepresents text directly targeted by the document URL's fragment. Not totally clear what this means. See the W3C reference. There is no browser support as of Jan 2021, but Chrome has expressed an intent to ship. When it's out, the possibilities will become clear.

One thing that is clear in the spec is that because of privacy concerns the page must not be able to read the styling of these pseudo-elements (similar to :visited.)

::spelling-error and ::grammar-error

What you'd expect. No browser support as of Jan 2021.

Tree-abiding pseudo-elements


Generates an element immediately before the originating element, containing the value specified in the content property (not generated if the value is none).


Generates an element immediately after the originating element, containing the value specified in the content property (not generated if the value is none).


::marker represents the marker box of a list item. Limited CSS properties available. Some browser support, so consider an enhancement.


::placeholder represents the text displayed as the placeholder. Compare this to the :placeholder-shown pseudo-class which selects elements that are displaying placeholder text. Limited to the same CSS properties as ::first-line.


This works in Firefox, but Webkit / Blink currently implement the non-standard ::-webkit-file-upload-button (Jan 2021).

Caption-based pseudo-elements (WebVTT)


Used without the optional selector, ::cue styles a WebVTT track's cues as if they were a single unit, except background styling, which is applied individually Good browser support.

::cue(selector) lacks Firefox support, so be careful.


Not entirely sure what the practical difference is between this and ::cue . No browser support info, so prolly not worth worrying about atm (Jan 2021).

Shadow DOM


Shadow hosts may use the HTML attribute part to expose selected Shadow tree elements outside of the Shadow root. The ::part pseudo-element allows them to be styled by page-level CSS. For example, if a Shadow element is exposed with part="foo", then the page CSS may style it with ::part(foo).

Limited styling of ::part with pseudo-classes is available, but only with those that use local element information, not those that are structural.

Good browser support.

If you're really digging into this, this more detailed explainer of ::part may be useful. Note that it's from Jan 2019, so parts (ha!) may be out of date (I can't find any other references to ::theme, for example, so this may have been dropped?).

Also, remember that CSS custom properties pierce the Shadow boundary so could be used in conjunction with these.


::slotted(selector), when used by Shadow DOM CSS, targets (only) element nodes placed into HTML template slots, which also match the specified selector.