CSS OR and AND Selectors: what we actually mean
CSS doesn’t have literal OR and AND keywords inside selectors, but we still talk about “OR
selectors” and “AND selectors” all the time.
It’s just shorthand for two patterns:
- OR = a comma-separated selector list (match either selector A or selector B).
- AND = combining conditions in one selector (match elements that satisfy condition A and condition B at the same time).
Think of OR as “multiple independent nets” and AND as “one net with multiple filters”. Let’s make that feel real with interactive examples.
.card,
.alert {
outline: 3px solid #0077b6;
}
.card.alert {
outline: 3px solid #ef233c;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.item {
border: 3px solid #111;
border-radius: 16px;
padding: 12px;
background: #fff;
box-shadow: 0 10px 0 #111;
min-height: 110px;
display: grid;
gap: 8px;
align-content: start;
}
.kicker {
font-size: 12px;
opacity: 0.75;
}
.tag {
display: inline-block;
padding: 2px 8px;
border: 2px solid #111;
border-radius: 999px;
font-size: 12px;
width: fit-content;
background: #f2f2f2;
}
Click the snippets: the first is OR (comma list), the second is AND (same element must match both).
class="card" Card Matches
.card.class="alert" Alert Matches
.alert.class="card alert" Card + Alert Matches
.cardand.alert.
In the OR snippet (.card, .alert), we style two different groups with one rule.
In the AND snippet (.card.alert), we only style elements that have both
classes at the same time.
CSS OR selector
The most common “OR selector” is a comma-separated list:
h2, h3means “select allh2elements OR allh3elements”..button, a.buttonmeans “select elements with classbuttonOR<a>elements with classbutton”.
Each selector on either side of the comma is evaluated independently. If an element matches any of them, it’s in.
h3,
a {
color: #0077b6;
text-decoration-thickness: 3px;
text-underline-offset: 3px;
}
h3,
a[href^="https"] {
color: #0077b6;
text-decoration-thickness: 3px;
text-underline-offset: 3px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 10px;
}
.panel {
border: 3px solid #111;
border-radius: 16px;
padding: 14px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 8px;
}
p {
margin: 0;
line-height: 1.45;
}
a {
color: inherit;
}
Headline (h3)
Links: https://example.com — /local
Try snippet 1: styles all
aandh3. Try snippet 2: stylesh3OR onlyawithhrefstarting withhttps.
The a[href^="https"] selector targets all <a> elements with an href attribute that starts with https.
Learn more in the CSS Attribute Selector Interactive Tutorial.
CSS selector OR condition and OR operator
When people say “OR condition” or “OR operator” in CSS selectors, they almost always mean: put multiple selectors in a selector list separated by commas.
Here’s a practical example: “highlight items that are featured OR new”.
.card.is-featured,
.card.is-new {
outline: 3px solid #0077b6;
}
.card.is-featured .badge,
.card.is-new .badge {
background: #0077b6;
color: #fff;
}
.card:is(.is-featured, .is-new) {
outline: 3px solid #0077b6;
}
.card:is(.is-featured, .is-new) .badge {
background: #0077b6;
color: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.card {
border: 3px solid #111;
border-radius: 16px;
padding: 12px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 8px;
min-height: 120px;
align-content: start;
}
.badge {
width: fit-content;
border: 2px solid #111;
border-radius: 999px;
padding: 2px 8px;
font-size: 12px;
background: #f2f2f2;
}
.kicker {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
Both snippets are “OR”: one uses commas, the other uses
:is()to group the OR list.normal Regular No extra state class.
is-new New Matches the OR condition.
is-featured Featured Matches the OR condition.
The comma list version is the classic “OR”.
The :is() version is still an “OR”, but it can make longer selectors easier to read because you avoid
repeating the left-hand side.
Learn more about :is() in the CSS :is() and :where() Pseudo-Classes Interactive Tutorial.
CSS selector OR class
“OR class” usually means: apply the same styles to elements that have class .primary OR
.secondary.
That’s a textbook selector list.
.button.primary,
.button.secondary {
background: #111;
color: #fff;
}
.button.primary .hint,
.button.secondary .hint {
opacity: 0.8;
}
.button.primary,
.button.secondary,
.button.ghost {
border-width: 3px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 10px;
}
.row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.button {
border: 3px solid #111;
border-radius: 999px;
padding: 10px 14px;
background: #fff;
cursor: pointer;
font: inherit;
display: inline-flex;
gap: 8px;
align-items: baseline;
}
.button.ghost {
background: transparent;
}
.hint {
font-size: 12px;
opacity: 0.65;
}
Snippet 1: “primary OR secondary”. Snippet 2: add a third option (ghost) to the OR list.
CSS selector OR attribute
Attribute selectors are perfect for OR lists because you can target multiple “types” of elements without writing
separate rules.
For example: “style inputs that are type email OR password”.
.field input[type="email"],
.field input[type="password"] {
outline: 3px solid #0077b6;
border-color: #0077b6;
}
.field input[required],
.field input[aria-invalid="true"] {
outline: 3px solid #ef233c;
border-color: #ef233c;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.fieldset {
border: 3px solid #111;
border-radius: 16px;
padding: 14px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 10px;
}
.field {
display: grid;
gap: 6px;
}
label {
font-size: 12px;
opacity: 0.85;
}
input {
border: 3px solid #111;
border-radius: 12px;
padding: 10px 12px;
font: inherit;
width: min(520px, 100%);
}
.note {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
Snippet 1: email OR password. Snippet 2: required OR aria-invalid.
CSS not OR selector
This is where people can get tricked (so you’re not alone). There are two common meanings of “not OR”:
- NOT (A OR B): exclude items that match either A or B.
- (NOT A) OR (NOT B): which is very different (and usually not what you want).
In CSS, :not() excludes elements that match its argument.
The argument can be a selector list (commas), which is incredibly handy.
.card:not(.is-disabled, .is-archived) {
outline: 3px solid #0077b6;
}
.card:not(.is-disabled):not(.is-archived) {
outline: 3px solid #0077b6;
}
.card:not(.is-disabled, .is-archived) .pill {
background: #0077b6;
color: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.card {
border: 3px solid #111;
border-radius: 16px;
padding: 12px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 8px;
align-content: start;
min-height: 130px;
}
.pill {
width: fit-content;
border: 2px solid #111;
border-radius: 999px;
padding: 2px 8px;
font-size: 12px;
background: #f2f2f2;
}
.kicker {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
All three snippets are valid ways to express “not disabled OR archived”.
normal Active Should be highlighted.
is-disabled Disabled Should be excluded.
is-archived Archived Should be excluded.
both Disabled + Archived Still excluded.
A useful rule of thumb:
:not(A, B) means “not A and not B”.
In other words, it excludes anything matching A OR B.
Learn more about :not() in the CSS :not Interactive Tutorial.
CSS selector AND condition and AND operator
“AND” means you’re adding more requirements to the same element. In selector terms, that’s usually done by chaining simple selectors with no spaces:
button.primary= an element that is abuttonand has classprimary.a[href^="https"][target="_blank"]= anatag and it has both attributes..card:hover= element with classcardand it’s hovered.
a[target="_blank"][href^="https"] {
outline: 3px solid #0077b6;
border-radius: 10px;
padding: 2px 6px;
}
a[target="_blank"][href^="https"]:hover {
outline: 3px solid #ef233c;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 10px;
}
.panel {
border: 3px solid #111;
border-radius: 16px;
padding: 14px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 8px;
}
a {
color: #111;
text-decoration-thickness: 2px;
text-underline-offset: 3px;
}
.note {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
External + blank — External only — Blank only
Snippet 1: requires both attributes (AND). Snippet 2: adds
:hoveras another AND condition.
CSS AND selector with two classes
This is the classic “AND” example: .chip.rounded.
No space means the same element must have both classes.
A space changes the meaning completely:
.chip .rounded means “a descendant with class rounded inside an element with class
chip”.
That’s not AND on one element anymore — that’s a relationship (we’ll cover combinators soon).
.chip.rounded {
background: #111;
color: #fff;
}
.chip .rounded {
background: #111;
color: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.chip {
border: 3px solid #111;
border-radius: 14px;
padding: 10px 12px;
background: #fff;
display: inline-flex;
gap: 8px;
align-items: center;
}
.rounded {
border-radius: 999px;
padding: 2px 8px;
border: 2px solid #111;
background: #f2f2f2;
font-size: 12px;
}
.note {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
One element has .chip and .roundedNested .rounded inside .chipChip only still nestedSnippet 1 (
.chip.rounded) targets only the first box. Snippet 2 (.chip .rounded) targets the inner pills inside any chip.
CSS selectors AND combinators
Combinators describe relationships between elements. They’re not “AND on one element”, but they do combine conditions across the DOM. The main combinators are:
- Descendant (space):
.list .item= any.iteminside.list. - Child (
>):.list > .item= only direct children. - Adjacent sibling (
+):.item + .item= an.itemimmediately after another. - General sibling (
~):.item ~ .item= any later siblings.
Where the “AND” idea shows up: you often chain conditions on one part of the selector and use a combinator.
Example: .list.is-compact > .item.is-active means:
the parent is .list AND .is-compact, and the child is .item AND
.is-active.
.list.is-compact > .item.is-active {
outline: 3px solid #0077b6;
}
.list.is-compact > .item.is-active .dot {
background: #0077b6;
}
.list.is-compact .item.is-active {
outline: 3px solid #0077b6;
}
.list.is-compact .item.is-active .dot {
background: #0077b6;
}
.item + .item {
border-top-width: 6px;
border-color: #0077b6;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.list {
border: 3px solid #111;
border-radius: 16px;
background: #fff;
box-shadow: 0 10px 0 #111;
overflow: hidden;
}
.head {
padding: 12px 14px;
border-bottom: 2px solid #111;
display: flex;
justify-content: space-between;
gap: 10px;
align-items: baseline;
}
.kicker {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
.items {
display: grid;
}
.item {
padding: 12px 14px;
border-top: 1px solid transparent;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 999px;
border: 2px solid #111;
background: #f2f2f2;
}
.badge {
font-size: 12px;
border: 2px solid #111;
border-radius: 999px;
padding: 2px 8px;
background: #f2f2f2;
}
.list.is-compact try the snippetsItem AItem B (active)Item CSnippet 1 uses
>(direct child). Snippet 2 uses a space (descendant). Snippet 3 uses+to style items that immediately follow another item.
Notice how combinators change which elements are eligible, while the chained parts (like
.item.is-active) define AND conditions on a single element.
Learn more about sibling selectors in the CSS Sibling Selector Interactive Tutorial, and learn more about the direct child selector in the CSS Direct Child Selector (>) Interactive Tutorial.
Practical tips for writing OR and AND selectors
Keep OR lists readable
- Put each selector on its own line (still separated by commas) when the list gets long.
-
If you’re repeating the same left side, consider grouping with
:is()to reduce repetition.
Common AND mistakes
-
Accidental descendant:
.a .bis not the same as.a.b. -
Wrong element:
.button.primarytargets the button element, not a child inside it. - Over-specific selectors: stacking too many conditions can make your CSS fragile.
Debugging trick: isolate the conditions
If a selector “doesn’t work”, split it into smaller tests:
first confirm .item matches, then .item.is-active, then add the combinator part.
CSS is very logical — it’s just very picky.
.item {
outline: 3px solid #0077b6;
}
.item.is-active {
outline: 3px solid #ef233c;
}
.list > .item.is-active {
outline: 3px solid #111;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.list {
border: 3px solid #111;
border-radius: 16px;
padding: 12px;
background: #fff;
box-shadow: 0 10px 0 #111;
display: grid;
gap: 10px;
}
.item {
border: 2px solid #111;
border-radius: 12px;
padding: 10px 12px;
background: #f7f7f7;
}
.item.is-active {
background: #fff;
}
.note {
font-size: 12px;
opacity: 0.75;
margin: 0;
}
Item 1Item 2 (active)Item 3Click snippets in order: first “does
.itemmatch?”, then “does AND with.is-activematch?”, then “does the parent/child relationship match?”.
Summary: the OR/AND cheat sheet
-
OR: use commas —
.a, .b, .c. -
AND on the same element: chain selectors with no space —
.a.b,a[href][target]. -
AND across structure: add combinators —
.parent > .child.is-active. -
NOT with OR: exclude a whole group —
:not(.disabled, .archived).
If you remember just one thing: commas are OR, and no spaces is AND (on the same element).
