CSS Element Selectors
An element selector (also called a type selector) matches every occurrence of a given HTML tag in the document and applies a set of styles to all of them at once. Instead of writing p or ul in your markup and hoping it looks right, you write p { ... } or ul { ... } in your stylesheet and the browser finds and styles every matching element automatically. Element selectors are the simplest, broadest tool in CSS, and understanding exactly how broad they are is the key to using them well.
They matter because almost every stylesheet starts with element selectors: setting a base font on body, spacing on p, list styling on ul and li, link colors on a. Get comfortable with them first, and class/ID selectors will make much more sense as refinements layered on top.
Overview / How it works
An element selector is written as the tag name itself, with no punctuation: p, div, h1, img, and so on. When the browser parses your CSS, it builds a list of rules, each with a selector and a declaration block. During rendering, for every element node in the DOM, the browser’s style engine checks which rules have a selector that matches that node, collects all the matching declarations, and resolves conflicts using the cascade (origin, specificity, and source order) before computing the element’s final styles.
An element selector matches purely by tag name — it does not care about the element’s position in the tree, its class, its id, or its attributes. This means p { color: navy; } matches every single <p> element on the page, whether it is the first paragraph in the document or nested three levels deep inside a <div> inside an <article>. That breadth is both the strength and the danger of element selectors: they are extremely convenient for setting sitewide defaults, but they apply everywhere a tag appears, including places you may not have anticipated.
In terms of specificity — the mechanism the browser uses to decide which rule wins when two rules target the same element — an element selector has the lowest specificity weight of any selector type (aside from the universal selector *, which has none at all). This means a single class or id selector will always beat an element selector targeting the same property, regardless of how the rules are ordered in the stylesheet. Element selectors are meant to be a base layer that classes and ids override.
Syntax
tagname {
property: value;
property: value;
}
- tagname — the HTML element name to match (e.g.
p,section,a,img). HTML tag names are matched case-insensitively, soDIVanddivselect the same elements. - { } — the declaration block, containing one or more
property: value;pairs. - Grouping — multiple element selectors can share one declaration block by separating them with commas:
h1, h2, h3 { ... }applies the same styles to all three tags. - Combining with combinators — element selectors are frequently chained with descendant (space), child (
>), and sibling (+,~) combinators to narrow the match, e.g.article pmatches only <p> elements inside an <article>.
Examples
Example 1: A basic type selector
p {
color: #1a1a1a;
line-height: 1.6;
margin-bottom: 1em;
}
Result: Every <p> element on the page — in the header, the main content, the footer, anywhere — gets dark gray text, generous line spacing, and a bottom margin. No class or id is needed on any paragraph for this to take effect.
This is the simplest possible use of an element selector: one tag name, one declaration block, applied globally. It’s a good way to set a sensible default for a tag before you start layering more specific rules on top of it.
Example 2: Grouping selectors for shared styles
h1, h2, h3 {
font-family: Georgia, 'Times New Roman', serif;
color: #202124;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
h1 {
font-size: clamp(1.75rem, 4vw, 2.75rem);
}
Result: All three heading levels share the same serif font, dark color, and vertical spacing rhythm, while <h1> additionally gets a font size that scales smoothly between roughly 28px and 44px depending on viewport width. Grouping avoids repeating identical declarations three times.
Grouping with commas is purely a convenience for the author — the browser treats h1, h2, h3 { ... } exactly as if you had written three separate rules with identical declarations. There’s no performance or specificity difference; it just keeps the stylesheet shorter and easier to maintain.
Example 3: Scoping a type selector with a combinator
nav ul {
list-style: none;
display: flex;
gap: 1.5rem;
}
nav ul li a {
text-decoration: none;
color: inherit;
font-weight: 600;
}
Result: Only <ul> elements that live inside a <nav> lose their bullets and lay out horizontally with 1.5rem of gap between items; a <ul> used elsewhere on the page (say, inside an <article>) is unaffected. Likewise, only the links inside that nav list lose their underline and inherit the surrounding text color in bold.
This example shows the real power move with element selectors: pairing plain tag names with combinators so you get the convenience of not needing classes on every link and list item, while still limiting the effect to a specific region of the page rather than every <ul> or <a> on the site.
How it works step by step
Consider the rule article p { margin-bottom: 1rem; } applied to a paragraph nested inside an article. The rendering engine’s matching process works roughly like this:
- 1. Parse the selector. The engine splits
article pinto two compound selectors joined by a descendant combinator: match a <p>, then require an <article> ancestor somewhere above it. - 2. Match right to left. Browsers evaluate compound selectors from the rightmost part first for efficiency. It first checks: is this node a <p>? If not, the rule doesn’t apply and matching stops immediately.
- 3. Walk up the tree. If the node is a <p>, the engine walks up through its ancestors looking for an <article>. If one is found at any depth, the whole selector matches.
- 4. Collect the declaration. The matched rule’s
margin-bottom: 1remis added to the set of declarations competing for this element’smargin-bottomproperty. - 5. Resolve the cascade. If another rule also sets
margin-bottomon this same <p> (say, a class like.tight), the browser compares specificity.article phas a specificity of two type selectors (roughly weight 0-0-2), while.tighthas one class (weight 0-1-0). Since any class outweighs any number of type selectors,.tightwins even thougharticle pis more specific-sounding in plain English. - 6. Compute and paint. The winning value is used in the box model calculation for that paragraph, and the final layout is painted to the screen.
Common Mistakes
Mistake 1: Styling a tag globally when you meant one instance
/* Intended to style just the links in the site footer,
but this affects every on every page */
a {
color: #cc0000;
text-decoration: none;
}
Because a is unscoped, it overrides the color of navigation links, body text links, and footer links all at once — usually not what the author wanted. The fix is to scope the selector to the actual container:
footer a {
color: #cc0000;
text-decoration: none;
}
Now the rule only reaches anchors that are descendants of <footer>, leaving links elsewhere on the page untouched.
Mistake 2: Expecting a type selector to skip nested elements
/* Meant to add spacing only to top-level list items,
but this also styles every nested sub-list item */
li {
margin-bottom: 0.75rem;
border-bottom: 1px solid #eee;
}
A type selector matches at any depth, so if a <ul> contains a nested <ul> with its own <li> items, those nested items get the same border and margin, which usually produces an unwanted stacked-line look inside sub-lists. Restrict the match with a child combinator so only direct children of the top-level list are affected:
.menu > li {
margin-bottom: 0.75rem;
border-bottom: 1px solid #eee;
}
Switching from a bare li to a scoped, direct-child selector keeps the styling confined to one level of the list.
Best Practices
- Use element selectors for sitewide defaults — base typography on
body, spacing onp, reset behavior onul/ol— and reach for classes when you need to style a subset of elements. - Scope broad tags like
a,button, andimgto a container (nav a,.card img) whenever the styling is meant for one region rather than the whole page. - Remember type selectors have the lowest specificity of any real selector, so don’t fight a class override by piling on more tag names — use a class instead.
- Group related tags with commas (
h1, h2, h3) to avoid duplicating identical declaration blocks. - Prefer a child combinator (
>) over a bare descendant selector when you specifically want to avoid affecting nested instances of the same tag. - Keep your reset/base rules (element selectors) early in the stylesheet and your component-specific rules later, so the natural reading order matches the cascade’s source-order tiebreaker.
Practice Exercises
- Exercise 1: Write a single rule using a grouped element selector that gives every <h1>, <h2>, and <h3> on a page the same
font-familyand aletter-spacingof0.02em. - Exercise 2: You have a <table> of pricing data and a separate <table> used for page layout elsewhere on the same page. Write an element selector, scoped so it only affects cells inside the pricing table, that gives every <td> inside it
padding: 0.5remand a bottom border. - Exercise 3: A <blockquote> on your page contains a nested <blockquote> (a quote within a quote). Write CSS so only the outer blockquote gets a left border and italic text, without that styling being inherited or reapplied by the nested one. (Hint: think about which combinator limits the match to direct children versus all descendants.)
Summary
- An element selector (type selector) matches every instance of a given tag name in the document, regardless of nesting depth.
- Tag names in selectors are matched case-insensitively in HTML.
- Multiple tag names can share one declaration block by grouping them with commas.
- Element selectors have the lowest specificity of any real selector (weight roughly 0-0-1 per tag), so a single class or id will always override them on a conflicting property.
- Combinators (descendant space, child
>) let you scope a type selector to a specific region or nesting level instead of the whole document. - Use element selectors for broad, sitewide base styling; reach for classes when you need to target a subset of elements sharing a tag.
