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
Badge*applies an outline, you can see how many elements are on the page.
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
phas a higher specificity than*. - A class selector like
.notebeatsp. - An ID selector like
#herobeats pretty much everything except!importantbattles.
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;
}
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::beforeand::aftermatch pseudo-elementsbox-sizing: border-boxmakes widths/heights behave more intuitively for many layouts
*,
::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.Togglebox-sizingto 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
Badge.keep-normal.
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:
-
Another rule overrides it.
Remember:
*is low specificity, so almost anything can win. -
You’re targeting the wrong place.
Maybe you wrote
.card *but the element is not inside.card. -
You’re changing a property that doesn’t apply.
Example:
widthon an inline element often doesn’t behave like you expect. - A component / iframe / shadow DOM is isolating styles. Some content can be outside the reach of your page CSS.
- 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, likebox-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, usinggappaired withdisplay: flexordisplay: gridis 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::beforeand::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.
