What is a CSS type selector

A CSS type selector targets elements by their tag name. It’s also commonly called an element selector or tag selector.

Examples:

  • p targets every <p> on the page.
  • h2 targets every <h2>.
  • button targets every <button>.

Type selectors are often your first step into CSS because they’re simple and readable. But they’re also easy to overuse, which can lead to “why is everything styled?!” moments.

p {
  color: #0b5fff;
}
  
h2 {
  color: #b10f2e;
}
  
button {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-monospace, system-ui, sans-serif;
  padding: 16px;
  border: 2px solid #111;
  border-radius: 12px;
  display: grid;
  gap: 12px;
  max-width: 620px;
}

button {
  border: 0;
  padding: 10px 14px;
  border-radius: 10px;
  cursor: pointer;
}

h2 {
  margin: 0;
  font-size: 20px;
}

p {
  margin: 0;
  line-height: 1.45;
}
  

Type selectors target tags

This paragraph is a <p> element.

This is another <p> element.

In that playground, each snippet targets a different tag name. No classes. No IDs. Just the element type.

Your first type selectors

Let’s style a tiny “mini page” using only type selectors. This will show you both the power and the danger: it’s very easy to style a lot of elements very quickly.

h1 {
  font-size: 28px;
  letter-spacing: 0.02em;
}

p {
line-height: 1.6;
}

a {
color: #0b5fff;
text-decoration-thickness: 2px;
text-underline-offset: 3px;
}

button {
background: #111;
color: #fff;
} 
a {
  color: #0b5fff;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}
  
button {
  background: #0b5fff;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.page {
  font-family: system-ui, ui-monospace, sans-serif;
  max-width: 720px;
  padding: 18px;
  border: 2px solid #111;
  border-radius: 14px;
}

h1 {
  margin: 0;
}

p {
  margin: 0;
}

.stack {
  display: grid;
  gap: 12px;
  margin-top: 10px;
}

button {
  border: 0;
  padding: 10px 14px;
  border-radius: 10px;
  cursor: pointer;
  width: fit-content;
}
  

Welcome to Tiny Page Land

This is a paragraph with a link inside it.

Another paragraph, because paragraphs like company.

Type selectors are great when you want consistent base styles. But if you style p globally, you’re styling every paragraph everywhere. That’s why you’ll often see type selectors used for “defaults” and then more specific selectors used for special cases.

Grouping type selectors with commas

If multiple element types should share the same rules, group them using commas.

  • h1, h2, h3 targets all three heading types.
  • input, textarea, select targets all those form controls.
h2,
h3 {
  color: #0b5fff;
}
  
h2,
h3 {
  border-left: 6px solid #111;
  padding-left: 10px;
}
  
p,
li {
  line-height: 1.7;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  padding: 16px;
  border: 2px solid #111;
  border-radius: 14px;
  max-width: 720px;
}

h2,
h3 {
  margin: 0;
}

p {
  margin: 10px 0 0;
}

ul {
  margin: 10px 0 0;
  padding-left: 22px;
}

li {
  margin: 6px 0;
}
  

Grouped heading styles

This is an h3

This is a paragraph.

  • This is a list item.
  • So is this.

Grouping is mostly about keeping your CSS shorter and easier to maintain. If you catch yourself copy-pasting the same rules across multiple selectors, commas are your friend.

Combining type selectors with classes and IDs

You can combine a type selector with a class or ID to be more specific:

  • p.note means “paragraphs that have the class note”.
  • button.primary means “buttons with the class primary”.
  • p#intro means “the paragraph with the ID intro”.

This is one of the best ways to avoid styling the entire universe when you only wanted to style one galaxy.

p.note {
  background: #fff2c2;
  padding: 10px 12px;
  border-radius: 12px;
}
  
button.primary {
  background: #0b5fff;
  color: #fff;
}
  
p#intro {
  font-weight: 700;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  padding: 16px;
  border: 2px solid #111;
  border-radius: 14px;
  display: grid;
  gap: 12px;
  max-width: 720px;
}

p {
  margin: 0;
  line-height: 1.6;
}

button {
  border: 0;
  padding: 10px 14px;
  border-radius: 10px;
  cursor: pointer;
  width: fit-content;
  background: #111;
  color: #fff;
}
  

I am the intro paragraph.

This is just a normal paragraph.

This paragraph has the class "note".

Notice how p.note is safer than p if you only want “note styling” sometimes. It won’t touch every paragraph on the site.

Descendant vs child with type selectors

Type selectors become much more useful when you combine them with relationships.

Descendant selector

nav a means “any link inside a <nav>, at any depth.”

Child selector

nav > a means “links that are direct children of <nav>.”

Learn more about the > selector in the CSS Direct Child Selector (>) Interactive Tutorial.

nav a {
  color: #0b5fff;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
  
nav > a {
  background: #111;
  color: #fff;
  padding: 6px 10px;
  border-radius: 999px;
  text-decoration: none;
}
  
nav ul a {
  font-style: italic;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  padding: 16px;
  border: 2px solid #111;
  border-radius: 14px;
  max-width: 760px;
  display: grid;
  gap: 12px;
}

nav {
  border: 2px dashed #777;
  padding: 12px;
  border-radius: 12px;
  display: grid;
  gap: 10px;
}

nav a {
  color: #111;
}

ul {
  margin: 0;
  padding-left: 18px;
}

li {
  margin: 6px 0;
}
  

Outside the nav: this link should not be affected by nav selectors.

Play with the snippets and watch which links get targeted. This is where type selectors start feeling like real “targeting tools,” not just “style every paragraph ever.”

Multiple type selectors in a chain

You can chain type selectors together to describe a structure.

  • article h2 targets headings inside an article.
  • article p targets paragraphs inside an article.
  • article a targets links inside an article.

This is a gentle way to “scope” styles without needing a million extra classes.

article h2 {
  color: #0b5fff;
}
  
article p {
  line-height: 1.7;
}
  
article a {
  color: #b10f2e;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.layout {
  font-family: system-ui, ui-monospace, sans-serif;
  display: grid;
  gap: 14px;
  max-width: 820px;
}

article,
aside {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 14px;
}

h2 {
  margin: 0;
  font-size: 20px;
  color: #111;
}

p {
  margin: 10px 0 0;
}

a {
  color: #111;
}
  

Notice how we didn’t style h2, p, or a everywhere. We only styled them inside article.

Type selectors for forms

Forms are full of element types that are perfect for type selectors: label, input, textarea, select, and button.

A very common beginner pattern is: “make all inputs look consistent.” Type selectors shine here.

label {
  font-weight: 700;
}
  
input,
textarea,
select {
  border: 2px solid #111;
  border-radius: 10px;
  padding: 10px 12px;
}
  
input:focus,
textarea:focus,
select:focus {
  outline: 3px solid #0b5fff;
  outline-offset: 2px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.form {
  font-family: system-ui, ui-monospace, sans-serif;
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
  max-width: 760px;
  display: grid;
  gap: 12px;
}

.row {
  display: grid;
  gap: 6px;
}

input,
textarea,
select {
  font: inherit;
}

button {
  border: 0;
  background: #111;
  color: #fff;
  padding: 10px 14px;
  border-radius: 10px;
  cursor: pointer;
  width: fit-content;
}
  

This is a perfect “type selector use case” because form elements are consistent categories: all inputs should behave similarly.

Type selectors with pseudo-classes and pseudo-elements

You can attach pseudo-classes and pseudo-elements to type selectors.

  • a:hover means “links when hovered”.
  • p::first-letter targets the first letter of a paragraph.
  • li::marker targets the bullet or number of a list item.
a:hover {
  text-decoration-thickness: 4px;
  text-underline-offset: 4px;
}
  
p::first-letter {
  font-size: 42px;
  font-weight: 700;
  padding-right: 6px;
}
  
li::marker {
  color: #0b5fff;
  font-weight: 700;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
  max-width: 820px;
  display: grid;
  gap: 12px;
}

p {
  margin: 0;
  line-height: 1.7;
}

a {
  color: #0b5fff;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

ul {
  margin: 0;
  padding-left: 22px;
}

li {
  margin: 6px 0;
}
  

Paragraphs can have fancy first letters. Also, here is a hoverable link.

  • Markers can be styled too.
  • Blue bullets feel oddly confident.

This is still “type selector territory” because the base selector is a tag name: a, p, li.

Learn more about :hover in the CSS :hover Pseudo-Class Interactive Tutorial, and about ::marker in the CSS List Style Interactive Tutorial.

Type selectors with attribute selectors

Attribute selectors are not type selectors, but they combine beautifully with them.

A classic example: target only certain inputs by their type attribute.

  • input targets all inputs.
  • input[type="email"] targets only email inputs.
  • input[type="checkbox"] targets only checkboxes.
input[type="email"] {
  border-color: #0b5fff;
}
  
input[type="password"] {
  border-color: #b10f2e;
}
  
input[type="checkbox"] {
  accent-color: #0b5fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
  max-width: 820px;
  display: grid;
  gap: 12px;
}

.row {
  display: grid;
  gap: 6px;
}

input {
  font: inherit;
  border: 2px solid #111;
  border-radius: 10px;
  padding: 10px 12px;
}

.small-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

label {
  font-weight: 700;
}
  

Think of this as: start with the element type (input), then filter it down using attributes.

Learn more about attribute selectors in the CSS Attribute Selector Interactive Tutorial.

Specificity and why type selectors are lightweight

CSS decides which rule wins using specificity (and also source order).

Type selectors have low specificity. That’s usually a good thing, because it keeps your CSS flexible.

  • p has lower specificity than .note.
  • p has lower specificity than #intro.
  • article p is still “type selector level”, just scoped.

This often results in a healthy pattern:

  1. Use type selectors for base styles.
  2. Use classes for components and variations.
  3. Use IDs rarely (mostly for anchors and JS hooks).
p {
  color: #0b5fff;
}
  
.note {
  color: #b10f2e;
}
  
#special {
  color: #111;
  font-weight: 700;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: system-ui, ui-monospace, sans-serif;
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
  max-width: 760px;
  display: grid;
  gap: 10px;
}

p {
  margin: 0;
  line-height: 1.6;
  padding: 10px 12px;
  border-radius: 12px;
  background: #f2f2f2;
}
  

Just a paragraph. It follows the p rule.

This one has a class, so it can override the p rule.

This one has an ID, which is even stronger.

That’s not “because IDs are better.” It’s simply how the rules are designed. Most modern CSS prefers classes for styling because they’re reusable and easier to manage.

Learn more about specificity in the CSS Specificity Interactive Tutorial.

CSS type selector not working

If your type selector feels like it’s being ignored, it’s usually one of these reasons:

Check you targeted the right element

  • header targets <header>, not a class named header.
  • .header targets a class, not the element type.
  • #header targets an ID, not the element type.

Check for more specific rules

  • A class selector like .card p can override p.
  • An ID selector like #content p can override p.
  • Inline styles can override almost everything.

Check source order

If two selectors have the same specificity, the one that appears later usually wins. This is part of the CSS cascade.

Learn more in the CSS Cascade Interactive Tutorial.

Check for resets or component styles

Some CSS frameworks apply base styles that may conflict with your type selectors. It’s normal. Just make your selector a bit more specific or scope it to a container.

p {
  color: #0b5fff;
}
  
.card p {
  color: #b10f2e;
}
  
.card p {
  color: #b10f2e;
}

p {
color: #0b5fff;
} 
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  font-family: system-ui, ui-monospace, sans-serif;
  max-width: 860px;
  display: grid;
  gap: 14px;
}

.card,
.plain {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
}

p {
  margin: 0;
  line-height: 1.6;
}
  

Plain area: just a paragraph.

Card area: paragraph inside .card.

Try the third snippet: it has the same two selectors, but reversed order. That’s a very common “wait, why did it change?” moment.

Best practices for type selectors

Use type selectors for base styles

Great targets for base styling:

  • body for font and general colors
  • a for default link styling
  • button for basic button behavior
  • img for responsive defaults like max-width: 100%

Scope when you can

Instead of styling p everywhere, style .article p or article p. This avoids affecting things like footers, sidebars, modals, and random widgets.

Avoid styling rare elements globally

Some elements appear in unexpected places (especially inside third-party embeds). If you globally style something like svg or input, it can have surprising side effects. Prefer scoping to a container when possible.

When you want zero specificity use :where

This is a pro-tip: :where() gives the selector zero specificity, which makes it very easy to override later.

Example idea: :where(article) p behaves like “style paragraphs in articles” but stays lightweight.

:where(article) p {
  color: #0b5fff;
}
  
:where(article) p {
  color: #0b5fff;
}

article p.note {
color: #b10f2e;
} 
article p {
  color: #0b5fff;
}

article p.note {
  color: #b10f2e;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.layout {
  font-family: system-ui, ui-monospace, sans-serif;
  display: grid;
  gap: 14px;
  max-width: 860px;
}

article {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 16px;
}

p {
  margin: 0;
  line-height: 1.6;
  padding: 10px 12px;
  border-radius: 12px;
  background: #f2f2f2;
}

.note {
  background: #fff2c2;
}
  

Normal article paragraph.

Article paragraph with a class.

The key idea is not “you must use :where().” It’s: type selectors are naturally lightweight, and :where() can make them even easier to override when building scalable CSS.

Learn more in the CSS :is() and :where() Pseudo-Classes Interactive Tutorial.

Wrap up

CSS type selectors are the simplest selectors: they target elements by tag name. They’re perfect for:

  • Base typography and default element styling
  • Consistent form control styles
  • Scoped styling like article p or nav a

Just remember the golden rule: type selectors are powerful because they match a lot of elements. When you want precision, combine them with structure (descendant/child) or with classes.