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 .card and .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, h3 means “select all h2 elements OR all h3 elements”.
  • .button, a.button means “select elements with class button OR <a> elements with class button”.

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 a and h3. Try snippet 2: styles h3 OR only a with href starting with https.

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.

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 a button and has class primary.
  • a[href^="https"][target="_blank"] = an a tag and it has both attributes.
  • .card:hover = element with class card and 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 + blankExternal onlyBlank only

Snippet 1: requires both attributes (AND). Snippet 2: adds :hover as 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 .rounded
Nested .rounded inside .chip
Chip only still nested

Snippet 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 .item inside .list.
  • Child (>): .list > .item = only direct children.
  • Adjacent sibling (+): .item + .item = an .item immediately 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 snippets
Item A
Item B (active)
Item C

Snippet 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 .b is not the same as .a.b.
  • Wrong element: .button.primary targets 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 1
Item 2 (active)
Item 3

Click snippets in order: first “does .item match?”, then “does AND with .is-active match?”, 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).