What is the CSS universal selector?

The CSS universal selector is written as * (a literal asterisk). It matches every element in the document (or in whatever scope you put it in).

* {
  outline: 2px dashed #111;
}
  
* {
  outline: none;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo-wrap {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.card {
  border: 3px solid #111;
  padding: 12px;
  border-radius: 12px;
  background: #f5f5f5;
}

.card h3 {
  margin: 0 0 8px;
}

.card p {
  margin: 0;
}

.badge {
  display: inline-block;
  padding: 4px 10px;
  border: 2px solid #111;
  border-radius: 999px;
  background: #fff;
  margin-top: 10px;
}
  

Universal selector demo

When * applies an outline, you can see how many elements are on the page.

Badge

Learn more about specificity in the CSS Specificity Interactive Tutorial.

CSS universal selector explained

* matches all elements. That includes div, p, button, input, and so on. It does not match “text nodes” (the raw text between tags), because CSS selectors target elements. By it-self, it also does not match pseudo-elements like ::before and ::after.

You can also use the universal selector inside another selector to scope it. That’s often the better move:

  • .card * = “everything inside .card
  • .card > * = “every direct child of .card
  • section * + * = “every element in a section that has an element right before it” (handy for spacing patterns)
.card * {
  outline: 2px solid #111;
}
  
.card > * {
  outline: 2px solid #111;
}
  
.card * + * {
  margin-top: 12px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
  display: grid;
  gap: 14px;
}

.card {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
}

.card h3 {
  margin: 0;
}

.card p {
  margin: 0;
}

.card button {
  border: 2px solid #111;
  background: white;
  padding: 8px 12px;
  border-radius: 10px;
  cursor: pointer;
}
  

Card title

This paragraph is inside the card.

Another card

Try each snippet. Notice the difference between .card * and .card > *.

Universal selector and pseudo-elements

Pseudo-elements like ::before and ::after aren’t “real elements” in the HTML, but they can be styled. A common pattern is to include them in a “global baseline”:

* (elements) + ::before + ::after (pseudo-elements).

*,
::before,
::after {
  box-sizing: border-box;
}
  
.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.box {
  width: 240px;
  padding: 16px;
  border: 8px solid #111;
  background: #f2f2f2;
  position: relative;
}

.box::before {
  content: "Pseudo-element";
  position: absolute;
  inset: -14px auto auto -14px;
  border: 2px solid #111;
  background: white;
  padding: 4px 8px;
  border-radius: 10px;
  font-size: 14px;
}
  
Select all elements and pseudo-elements with *, ::before, ::after.

CSS universal selector specificity

Specificity answers: “If two rules fight over the same property, who wins?”

  • The universal selector * has the lowest possible specificity (it contributes 0).
  • A type selector like p has a higher specificity than *.
  • A class selector like .note beats p.
  • An ID selector like #hero beats pretty much everything except !important battles.

This is why * { color: red; } is easy to override. It’s like shouting instructions in a room where everyone else has a megaphone.

* {
  color: #b00020;
}
p {
  color: #111;
}
.note {
  color: #0a5;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
}

p {
  margin: 0 0 10px;
}

.note {
  padding: 10px 12px;
  border: 2px solid #111;
  border-radius: 12px;
  background: #fff;
}
  

This is a normal paragraph.

This paragraph has a class: .note.

Specificity of scoped universal selectors

When you write something like .card *, the specificity is not “just universal”. The * still adds nothing, but the .card adds class specificity.

  • * = very low specificity
  • .card * = class specificity (because of .card)
  • .card p = class + type (slightly higher than just class alone)
.card * {
  color: #b00020;
}
  
.card p {
  color: #111;
}
  
.card .loud {
  color: #0a5;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.card {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
}

.card p {
  margin: 0 0 10px;
}

.loud {
  padding: 8px 10px;
  border: 2px solid #111;
  border-radius: 12px;
  background: #fff;
}
  

Paragraph inside card

Paragraph with class inside card

CSS universal selector example

Let’s do a real-world-ish example: a small “card” component where we want consistent spacing between all children, without writing CSS for every tag type.

The pattern .card > * + * is popular: it adds spacing between siblings, but doesn’t add spacing above the first child.

.card > * + * {
  margin-top: 12px;
}
  
.card > * + * {
  margin-top: 24px;
}
  
.card > * + * {
  margin-top: 0px;
}
  
*, ::before, ::after {
  box-sizing: border-box;
}
.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}
.card {
  border: 3px solid #111;
  border-radius: 16px;
  background: #f7f7f7;
  padding: 14px;
  max-width: 410px;
  display:flex;
  flex-direction: column;
}
.card h3 {
  margin: 0;
}
p {
    margin: 0;
}
.card a {
  color: #111;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.card button {
  border: 2px solid #111;
  background: white;
  padding: 10px 12px;
  border-radius: 12px;
  cursor: pointer;
  width: fit-content;
}
  

Card title

This card uses a universal-selector pattern to create vertical rhythm.

A link for flavor

CSS universal selector reset

A CSS reset is a set of baseline styles that make the browser’s default styling more predictable. Universal selectors are often part of these resets because they apply broadly.

The box-sizing reset

This is the famous one:

  • * matches all elements
  • ::before and ::after match pseudo-elements
  • box-sizing: border-box makes widths/heights behave more intuitively for many layouts
box-sizing baseline:
*,
::before,
::after {
  box-sizing: border-box;
}
  
.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.box {
  width: 180px;
  padding: 16px;
  border: 10px solid #111;
  background: #f2f2f2;
  border-radius: 14px;
}

.box strong {
  display: block;
  margin-bottom: 8px;
}

.hint {
  font-size: 14px;
  margin-top: 10px;
}
  
Box A Same width, padding, and border.
Toggle box-sizing to see the size behavior.
Box B The layout feels different when the border is counted outside the width.

A note on margin and padding resets

You’ll sometimes see: * { margin: 0; padding: 0; }

It’s simple, but it can be a little heavy-handed for beginners because it removes useful defaults (like spacing on headings and paragraphs). Many modern resets prefer to reset margins in a more targeted way, or apply spacing through a layout system.

If you do use it, consider scoping it (like .app *) or pairing it with intentional spacing rules.

* {
  margin: 0;
  padding: 0;
}
  
* {
  margin: 0;
  padding: 0;
}

.content > * + * {
  margin-top: 12px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.content {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
  max-width: 520px;
}

.content h3 {
  font-size: 20px;
}

.content p {
  line-height: 1.5;
}

.content a {
  color: #111;
  text-decoration: underline;
  text-underline-offset: 3px;
}
  

Reset demo

Default margins are gone. That can feel “clean”… and also weirdly cramped.

Adding .content > * + * brings back a predictable rhythm.

CSS override universal selector

Because * has very low specificity, overriding it is usually easy. Here are the main tools:

  • Source order: if two rules have equal specificity, the later rule wins.
  • More specific selectors: classes, types, IDs, etc.
  • !important: a last resort for breaking ties (use carefully).
* {
  color: #b00020;
}
  
* {
  color: #b00020;
}

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

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
}

p {
  margin: 0;
}
  

Universal sets a color, but more specific rules can override it.

Overriding a scoped universal rule

If you wrote .card *, it’s already “class-level specific”, so you’ll generally override it with: .card p, .card .something, or a rule that comes later with equal-or-higher specificity.

.card * {
  background: #ffe8e8;
}
  
.card * {
  background: #ffe8e8;
}

.card .keep-normal {
  background: transparent;
}
  
.card * {
  background: #ffe8e8;
}

.card p {
  background: #e8fff1;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.card {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
  max-width: 520px;
}

.card p {
  margin: 0 0 10px;
  padding: 6px 8px;
  border-radius: 10px;
}

.badge {
  display: inline-block;
  padding: 6px 10px;
  border: 2px solid #111;
  border-radius: 999px;
  background: #fff;
}
  

This paragraph may get overridden.

This paragraph has .keep-normal.

Badge

CSS universal selector performance

Let’s talk performance.

  • Good news: modern browsers are very fast at matching selectors.
  • Still true: * matches a lot of elements, so if you apply expensive properties everywhere, you can make the browser do extra work.

What counts as “expensive”? Things like heavy visual effects (filter, huge box-shadow), frequent layout changes, or animations applied to everything.

The safest approach is:

  • Use universal selectors for cheap baseline rules (like box-sizing).
  • Prefer scoped universal selectors for component-level rules (.card * or .app *).
  • Avoid styling everything with costly effects “just because you can”.
* {
  box-sizing: border-box;
}
  
* {
  box-sizing: border-box;
  filter: drop-shadow(0px 0px 8px rgba(0, 0, 0, 0.35));
}
  
.panel * {
  filter: drop-shadow(0px 0px 8px rgba(0, 0, 0, 0.35));
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
  display: grid;
  gap: 14px;
}

.panel {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
  max-width: 560px;
}

.panel p {
  margin: 0 0 10px;
}

.pill {
  display: inline-block;
  border: 2px solid #111;
  border-radius: 999px;
  padding: 6px 10px;
  background: #fff;
}
  

Compare “apply to everything” vs “apply only inside the panel”.

One Two Three

CSS universal selector not working

When someone says “the universal selector isn’t working”, it’s usually one of these:

  1. Another rule overrides it. Remember: * is low specificity, so almost anything can win.
  2. You’re targeting the wrong place. Maybe you wrote .card * but the element is not inside .card.
  3. You’re changing a property that doesn’t apply. Example: width on an inline element often doesn’t behave like you expect.
  4. A component / iframe / shadow DOM is isolating styles. Some content can be outside the reach of your page CSS.
  5. You’re looking at cached CSS or a different file. The oldest bug of all: “I saved… right?” (We’ve all been there.)

Debugging trick: make the effect obvious

If you’re unsure whether your CSS is applying, temporarily use a loud debugging style like outline (it doesn’t affect layout, and it’s very visible).

* {
  outline: 3px solid #b00020;
}
  
.wrapper * {
  outline: 3px solid #b00020;
}
  
.wrapper > * {
  outline: 3px solid #b00020;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
  display: grid;
  gap: 12px;
}

.wrapper {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
}

.wrapper p {
  margin: 0 0 10px;
}

.wrapper button {
  border: 2px solid #111;
  background: white;
  padding: 10px 12px;
  border-radius: 12px;
  cursor: pointer;
}
  

Try the different snippets to see what’s being targeted.

Best practices for the universal selector

  • Use * for baseline rules that are safe and cheap, like box-sizing.
  • Scope it when you can: .component * is often better than *.
  • Use “spacing between siblings” patterns like .stack > * + * to avoid styling every element type. (Although nowadays, using gap paired with display: flex or display: grid is often an even better choice.)
  • Don’t fight the cascade: if you need to override * often, your universal rule might be too broad.

Extra: the :where(*) pattern

If you want a selector that’s intentionally easy to override, :where() is designed to have zero specificity, even if you put more complex selectors inside it.

For example, :where(.card *) stays “easy to override”, which can be useful for base component styling.

:where(.card *) {
  color: #b00020;
}
  
:where(.card *) {
  color: #b00020;
}

.card p {
  color: #111;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 16px;
}

.card {
  border: 3px solid #111;
  border-radius: 14px;
  background: #fafafa;
  padding: 14px;
  max-width: 520px;
}

.card p {
  margin: 0;
}
  

:where(.card *) applies, but is easy to override.

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

Wrap-up: universal selector cheat sheet

  • * matches every element in scope.
  • * has very low specificity, so it’s easy to override.
  • Use it for baseline rules like box-sizing, preferably with ::before and ::after.
  • Prefer scoped patterns like .card * or .stack > * + * for real projects.
  • If “it’s not working”, check overrides, scope, and whether your CSS file is actually being applied.