Combining basic selectors with different combinators lets you describe structural relationships between elements precisely. Understand these four combinators, add a set of specificity calculation rules, and you can predict whether any CSS rule will actually take effect.
Descendant Combinator (space)
Two selectors separated by a space match a descendant element at any depth inside the former, without requiring direct nesting.
.article p {
color: #5c5872;
}
Whether p is a direct child of .article or wrapped in several layers of div, it will match.
Child Combinator (>)
Matches only direct children and does not pierce deeper levels, often used to avoid styles accidentally leaking into nested components.
.nav > li {
display: inline-block;
}
Practice tipWhen writing component library styles, prefer the child combinator over the descendant combinator to keep styles from "seeping" into same-named tags nested inside components.
Adjacent Sibling Combinator (+)
Matches the next sibling that immediately follows the previous element and shares the same parent.
h2 + p {
margin-top: 0;
}
Live preview: the paragraph right after a heading loses its top margin
Subheading
The paragraph right after it, with margin-top reset to zero.
General Sibling Combinator (~)
Matches every eligible sibling that appears after the previous element under the same parent, without requiring adjacency.
This is also the core principle behind the "pure CSS tabs / accordion" trick: link states through :checked with the general sibling combinator — no JavaScript needed.
Calculating Specificity
When several rules hit the same property of the same element, the browser decides which declaration wins by "specificity". Specificity is made of four tiers, from highest to lowest:
Tier
Example
Weight notation
Inline styles
style="color:red"
1,0,0,0
ID selectors
#nav
0,1,0,0
Class / attribute / pseudo-class
.btn / [type] / :hover
0,0,1,0
Type / pseudo-element
div / ::before
0,0,0,1
Compare tier by tier, and only look at the next tier when the higher ones tie; !important vaults past all of these tiers and should be used with care. For example, .nav .item.active has a weight of 0,0,2,0, higher than .item (0,0,1,0) but lower than #nav .active (0,1,1,0).
When two rules have exactly the same specificity, the "cascade" kicks in: the rule declared later overrides the one declared earlier. This is why the order of declarations in a CSS file, and the order in which <link> tags are included, affect the final result. Modern CSS has also introduced @layer cascade layers, which let you explicitly control the override relationships between styles from different sources — we mention it here for awareness and leave the details for an advanced topic.