Labs ICT

CSS Combinators

CSS combinators allow you to select elements based on their relationship to other elements. They enable precise targeting of elements in complex HTML structures without adding extra classes.

In this tutorial, we'll explore all CSS combinators including descendant, child, adjacent sibling, general sibling, and more advanced combinator techniques.

Descendant Combinator

The descendant combinator (space) selects all elements that are descendants of a specified element, regardless of how deeply nested they are.

Descendant Selector Syntax

/* Descendant combinator syntax */
ancestor descendant {
  /* Selects all descendant elements
  - Any level deep
  - Most common combinator
  */
}

/* Examples */
div p {
  color: #333;
  /* Selects all p elements inside div
  */
}

ul li {
  list-style-type: none;
  /* Selects all li elements inside ul
  */
}

.container .item {
  padding: 10px;
  /* Selects all .item elements inside .container
  */
}

Descendant Combinator Examples

/* Multiple levels */
.article p {
  line-height: 1.6;
  /* Selects p in .article, regardless of depth
  */
}

/* Nested structure */
.nav ul li a {
  text-decoration: none;
  color: #333;
  /* Selects all links in navigation
  - Multiple nesting levels
  */
}

/* Specific descendant selection */
.sidebar .widget .title {
  font-size: 1.2rem;
  font-weight: bold;
  /* Selects .title inside .widget inside .sidebar
  */
}

/* Form elements */
.form-group input[type="text"] {
  border: 1px solid #ddd;
  padding: 8px;
}

.form-group textarea {
  border: 1px solid #ddd;
  padding: 8px;
  resize: vertical;
}

Performance Considerations

/* Efficient descendant selectors */
.efficient-selector {
  /* Avoid overly specific descendant selectors
  - Can impact performance
  - Consider using classes
  */
}

/* Less efficient */
body div.container div.content div.article p {
  /* Too specific
  - Performance impact
  - Brittle
  */
}

/* More efficient */
.article-content p {
  /* More direct
  - Better performance
  - More maintainable
  */
}

Child Combinator

The child combinator (>) selects only direct children of a specified element, not grandchildren or deeper descendants.

Child Selector Syntax

/* Child combinator syntax */
parent > child {
  /* Selects only direct children
  - One level deep only
  - More specific than descendant
  */
}

/* Examples */
ul > li {
  margin-bottom: 5px;
  /* Selects only direct li children of ul
  - Not li inside nested ul
  */
}

.nav > a {
  display: block;
  padding: 10px;
  /* Selects only direct a children of .nav
  */
}

.container > .item {
  border: 1px solid #ddd;
  /* Selects only direct .item children
  */
}

Child vs Descendant

/* Child selector - more specific */
.menu > li {
  list-style: none;
  /* Only direct li children
  */
}

/* Descendant selector - less specific */
.menu li {
  list-style: none;
  /* All li descendants
  - Including nested ones
  */
}

/* HTML structure:

*/

/* .menu > li selects: Item 1, Item 2, Item 3 */
/* .menu li selects: Item 1, Item 2, Item 3, Nested Item 2.1, Nested Item 2.2 */

Child Combinator Examples

/* Navigation structure */
.nav > ul > li {
  display: inline-block;
  margin-right: 10px;
}

.nav > ul > li > a {
  color: #333;
  text-decoration: none;
  padding: 5px 10px;
}

/* Form structure */
.form-group > label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.form-group > input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
}

/* Card structure */
.card > .header {
  padding: 15px;
  background-color: #f8f9fa;
  border-bottom: 1px solid #ddd;
}

.card > .content {
  padding: 15px;
}

.card > .footer {
  padding: 15px;
  background-color: #f8f9fa;
  border-top: 1px solid #ddd;
}

/* Table structure */
.table > thead > tr > th {
  background-color: #f8f9fa;
  font-weight: bold;
  padding: 10px;
  border-bottom: 2px solid #ddd;
}

.table > tbody > tr > td {
  padding: 10px;
  border-bottom: 1px solid #ddd;
}

Adjacent Sibling Combinator

The adjacent sibling combinator (+) selects an element that immediately follows another element, and both must have the same parent.

Adjacent Sibling Syntax

/* Adjacent sibling syntax */
element + sibling {
  /* Selects sibling immediately after element
  - Same parent required
  - Direct adjacency required
  */
}

/* Examples */
h1 + p {
  font-weight: bold;
  color: #666;
  /* Selects p immediately after h1
  */
}

input:focus + .tooltip {
  display: block;
  /* Selects tooltip immediately after focused input
  */
}

.error + .error-message {
  color: #dc3545;
  /* Selects error message after error field
  */
}

Adjacent Sibling Examples

/* Form validation */
input:invalid + .error-message {
  display: block;
  color: #dc3545;
  font-size: 0.9rem;
  margin-top: 5px;
}

input:valid + .success-message {
  display: block;
  color: #28a745;
  font-size: 0.9rem;
  margin-top: 5px;
}

/* Headings and content */
h2 + p {
  font-size: 1.1rem;
  margin-top: 0;
  /* Paragraph immediately after h2
  */
}

h3 + h4 {
  margin-top: 0;
  /* h4 immediately after h3
  */
}

/* List items */
li:first-child + li {
  margin-top: 10px;
  /* All li except first
  - Alternative to :not(:first-child)
  */
}

/* Checkbox and label */
input[type="checkbox"] + label {
  cursor: pointer;
  padding-left: 5px;
}

/* Button groups */
.button + button {
  margin-left: 10px;
  /* Button immediately after another button
  */

Advanced Adjacent Patterns

/* Multiple adjacent selectors */
h1 + h2 + h3 {
  margin-top: 0;
  /* h3 immediately after h2 immediately after h1
  */
}

/* Class-based adjacent selectors */
.active + .content {
  display: block;
  /* Content immediately after active element
  */

.hidden + .visible {
  display: block;
  /* Visible element immediately after hidden
  */
}

/* State-based adjacent */
input:focus + label {
  font-weight: bold;
  /* Label immediately after focused input
  */
}

input:not(:placeholder-shown) + label {
  color: #28a745;
  /* Label after filled input
  */
}

General Sibling Combinator

The general sibling combinator (~) selects all elements that follow another element, sharing the same parent, but not necessarily immediately.

General Sibling Syntax

/* General sibling syntax */
element ~ sibling {
  /* Selects all siblings after element
  - Same parent required
  - Not necessarily adjacent
  */
}

/* Examples */
h1 ~ p {
  color: #666;
  /* All p elements after h1
  */
}

.error ~ .error-message {
  color: #dc3545;
  /* All error messages after error field
  */
}

.active ~ .content {
  display: block;
  /* All content after active element
  */
}

General Sibling Examples

/* Form validation messages */
input:invalid ~ .error-message {
  display: block;
  color: #dc3545;
  font-size: 0.9rem;
  margin-top: 2px;
}

/* Navigation active states */
.nav-item.active ~ .nav-item {
  opacity: 0.7;
  /* Dim all nav items after active one
  */
}

/* Accordion panels */
.accordion-trigger.active ~ .accordion-panel {
  display: block;
  /* All panels after active trigger
  */
}

/* Tab content */
.tab-trigger.active ~ .tab-content {
  display: block;
  /* All content after active tab
  */
}

/* List styling */
li:first-child ~ li {
  margin-top: 5px;
  /* All li except first
  - Alternative to :not(:first-child)
  */
}

/* Image captions */
img ~ figcaption {
  font-style: italic;
  color: #666;
  font-size: 0.9rem;
  margin-top: 5px;
  /* Caption after image
  */

Adjacent vs General Sibling

/* Adjacent sibling (+) - only immediate */
h2 + p {
  font-weight: bold;
  /* Only p immediately after h2
  */
}

/* General sibling (~) - all following */
h2 ~ p {
  color: #666;
  /* All p elements after h2
  */

/* HTML structure:

Heading

Paragraph 1

Other content

Paragraph 2

Paragraph 3

*/ /* h2 + p selects: Paragraph 1 only */ /* h2 ~ p selects: Paragraph 1, Paragraph 2, Paragraph 3 */

Advanced Combinators

CSS provides advanced combinators for more complex selection scenarios, including the :has() pseudo-class and compound selectors.

:has() Pseudo-Class

/* :has() pseudo-class syntax */
parent:has(child) {
  /* Selects parent containing specific child
  - Relational selection
  - Very powerful
  */
}

/* Examples */
.form-group:has(:invalid) {
  border: 1px solid #dc3545;
  /* Form group with invalid input
  */
}

.card:has(.featured) {
  border: 2px solid #007bff;
  /* Card containing featured content
  */
}

.nav-item:has(.active) {
  background-color: #f8f9fa;
  /* Nav item containing active element
  */
}

/* Complex :has() selectors */
.article:has(h2:first-child) {
  margin-top: 0;
  /* Article starting with h2
  */
}

.sidebar:has(.widget:last-child) {
  padding-bottom: 0;
  /* Sidebar with widget as last child
  */
}

.container:has(.highlight):hover .content {
  background-color: #fff3cd;
  /* Content when container has highlight and is hovered
  */
}

Compound Combinators

/* Multiple combinators */
.header > .nav > ul > li > a {
  color: #333;
  text-decoration: none;
  /* Direct link in nested structure
  */
}

/* Mixed combinators */
.article > h1 + p {
  font-size: 1.2rem;
  /* Paragraph immediately after h1 in article
  */
}

.sidebar > .widget ~ .widget {
  margin-top: 20px;
  /* Widgets after first widget in sidebar
  */
}

/* Complex combinations */
.form-group > label + input:focus {
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
  /* Input focused when immediately after label
  */
}

/* Multiple levels with different combinators */
.container > .item .content ~ .footer {
  font-size: 0.9rem;
  color: #666;
  /* Footer after content in item
  */
}

Practical Advanced Examples

/* Form validation with :has() */
.form-field:has(input:invalid) {
  margin-bottom: 20px;
}

.form-field:has(input:valid) {
  margin-bottom: 10px;
}

.form-field:has(input:required:placeholder-shown) {
  border-left: 3px solid #ffc107;
}

.form-field:has(input:required:not(:placeholder-shown)) {
  border-left: 3px solid #28a745;
}

/* Navigation states */
.nav-item:has(.active) {
  background-color: #007bff;
}

.nav-item:has(.active) a {
  color: white;
}

/* Content-based styling */
.article:has(.video) {
  padding: 20px;
  background-color: #f8f9fa;
}

.article:has(.gallery) {
  padding: 0;
  background-color: white;
}

/* Interactive states */
.button:has(.icon) {
  padding-left: 30px;
  position: relative;
}

.button:has(.icon)::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
}

/* Card layouts */
.card:has(.image) .title {
  margin-top: 10px;
}

.card:not(:has(.image)) .title {
  margin-top: 0;
}

Combinator Best Practices

βœ“ Do:

  • Choose the right combinator for the relationship
  • Consider performance with complex selectors
  • Test combinators across different browsers
  • Use combinators to reduce class dependencies
  • Document complex selector logic
  • Consider specificity when using combinators
  • Use :has() for parent selection when supported

βœ— Don't:

  • Overuse complex combinators unnecessarily
  • Nest combinators too deeply
  • Ignore browser compatibility for new features
  • Use combinators when simple classes would work
  • Create selectors that are too specific
  • Forget about performance implications
  • Use combinators without understanding relationships

Performance Considerations

⚑ Performance Tips:

  • Child > descendant: Child selectors are faster
  • Avoid deep nesting: Limit selector depth
  • Rightmost specificity: Most specific on the right
  • Universal selector: Avoid * in combinators
  • Tag qualifiers: Use classes when possible

Browser Compatibility

🌐 Compatibility Notes:

  • Descendant (space): Universal support
  • Child (>): Universal support
  • Adjacent sibling (+): Universal support
  • General sibling (~): Universal support
  • :has(): Modern browsers (Chrome 105+, Firefox 103+, Safari 15.4+)
  • Always provide fallbacks for :has()

Real-World Examples

Navigation Menu

/* Navigation with combinators */
.nav {
  background-color: #f8f9fa;
  padding: 0;
}

.nav > ul {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav > ul > li {
  position: relative;
}

.nav > ul > li > a {
  display: block;
  padding: 1rem 1.5rem;
  color: #333;
  text-decoration: none;
  transition: background-color 0.2s ease;
}

.nav > ul > li > a:hover {
  background-color: #e9ecef;
}

/* Dropdown menu */
.nav > ul > li > ul {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  min-width: 200px;
}

.nav > ul > li:hover > ul {
  display: block;
}

/* Active state */
.nav > ul > li.active > a {
  background-color: #007bff;
  color: white;
}

/* Dropdown items */
.nav > ul > li > ul > li > a {
  display: block;
  padding: 0.75rem 1rem;
  color: #333;
  text-decoration: none;
  border-bottom: 1px solid #f0f0f0;
}

.nav > ul > li > ul > li > a:hover {
  background-color: #f8f9fa;
}

Form Validation

/* Form validation with combinators */
.form-group {
  margin-bottom: 1.5rem;
  position: relative;
}

.form-group > label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: #333;
}

.form-group > input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  transition: border-color 0.2s ease;
}

/* Validation states */
.form-group:has(input:invalid) > input {
  border-color: #dc3545;
}

.form-group:has(input:valid) > input {
  border-color: #28a745;
}

.form-group:has(input:focus) > input {
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

/* Error messages */
.form-group > .error-message {
  display: none;
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.form-group:has(input:invalid) > .error-message {
  display: block;
}

/* Success messages */
.form-group > .success-message {
  display: none;
  color: #28a745;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.form-group:has(input:valid) > .success-message {
  display: block;
}

/* Required field indicators */
.form-group:has(input:required:placeholder-shown) > label::after {
  content: " *";
  color: #dc3545;
}

Card Layout

/* Card layout with combinators */
.card {
  border: 1px solid #dee2e6;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.card > .header {
  padding: 1.5rem;
  background-color: #f8f9fa;
  border-bottom: 1px solid #dee2e6;
}

.card > .header > .title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: #333;
}

.card > .header > .subtitle {
  margin: 0.5rem 0 0 0;
  font-size: 0.875rem;
  color: #666;
}

.card > .content {
  padding: 1.5rem;
}

.card > .content > p {
  margin: 0 0 1rem 0;
  line-height: 1.6;
}

.card > .content > p:last-child {
  margin-bottom: 0;
}

.card > .footer {
  padding: 1rem 1.5rem;
  background-color: #f8f9fa;
  border-top: 1px solid #dee2e6;
}

.card > .footer > .button {
  padding: 0.75rem 1.5rem;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
}

/* Card with image */
.card:has(.image) > .header {
  padding: 0;
}

.card:has(.image) > .image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.card:has(.image) > .header > .title {
  padding: 1rem;
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  position: absolute;
  bottom: 1rem;
  left: 1rem;
}

Complete Example

Comprehensive Combinator System

/* Combinator system variables */
:root {
  /* Spacing */
  --combinator-gap-sm: 0.5rem;
  --combinator-gap-md: 1rem;
  --combinator-gap-lg: 1.5rem;
}

/* Descendant selectors */
.container-text .content {
  line-height: 1.6;
  color: #333;
}

.sidebar .widget {
  margin-bottom: var(--combinator-gap-md);
  padding: var(--combinator-gap-md);
  background-color: #f8f9fa;
  border-radius: 4px;
}

.navigation .menu .item {
  padding: var(--combinator-gap-sm) var(--combinator-gap-md);
  text-decoration: none;
  color: #333;
  transition: background-color 0.2s ease;
}

.navigation .menu .item:hover {
  background-color: #e9ecef;
}

/* Child selectors */
.form-group > label {
  display: block;
  margin-bottom: var(--combinator-gap-sm);
  font-weight: 500;
}

.form-group > input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  transition: border-color 0.2s ease;
}

.card > .header {
  padding: var(--combinator-gap-md);
  background-color: #f8f9fa;
  border-bottom: 1px solid #dee2e6;
}

.card > .content {
  padding: var(--combinator-gap-md);
}

.card > .footer {
  padding: var(--combinator-gap-md);
  background-color: #f8f9fa;
  border-top: 1px solid #dee2e6;
}

/* Adjacent sibling selectors */
.heading + .subheading {
  margin-top: var(--combinator-gap-sm);
  font-size: 1.1rem;
  color: #666;
}

.input + .tooltip {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background-color: #333;
  color: white;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 0.875rem;
  z-index: 10;
}

.input:focus + .tooltip {
  display: block;
}

.error + .error-message {
  display: block;
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: var(--combinator-gap-sm);
}

.success + .success-message {
  display: block;
  color: #28a745;
  font-size: 0.875rem;
  margin-top: var(--combinator-gap-sm);
}

/* General sibling selectors */
.active ~ .content {
  display: block;
}

.hidden ~ .visible {
  opacity: 1;
}

.tab-trigger.active ~ .tab-content {
  display: block;
}

.tab-trigger:not(.active) ~ .tab-content {
  display: none;
}

/* Advanced :has() selectors */
.form-field:has(input:invalid) {
  border-left: 3px solid #dc3545;
  padding-left: var(--combinator-gap-md);
}

.form-field:has(input:valid) {
  border-left: 3px solid #28a745;
  padding-left: var(--combinator-gap-md);
}

.form-field:has(input:focus) {
  border-left: 3px solid #007bff;
  padding-left: var(--combinator-gap-md);
}

.card:has(.featured) {
  border: 2px solid #007bff;
  box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
}

.navigation:has(.active) {
  background-color: #f8f9fa;
}

.button:has(.icon) {
  padding-left: 2.5rem;
  position: relative;
}

.button:has(.icon) .icon {
  position: absolute;
  left: var(--combinator-gap-sm);
  top: 50%;
  transform: translateY(-50%);
}

/* Compound combinators */
.nav > ul > li > a {
  display: block;
  padding: var(--combinator-gap-md);
  color: #333;
  text-decoration: none;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.nav > ul > li > a:hover {
  background-color: #007bff;
  color: white;
}

.article > h1 + p {
  font-size: 1.2rem;
  font-weight: 500;
  margin-top: var(--combinator-gap-md);
}

.sidebar > .widget ~ .widget {
  margin-top: var(--combinator-gap-lg);
  border-top: 1px solid #dee2e6;
  padding-top: var(--combinator-gap-lg);
}

/* List styling */
.list > li {
  padding: var(--combinator-gap-sm) 0;
  border-bottom: 1px solid #f0f0f0;
}

.list > li:first-child {
  padding-top: 0;
}

.list > li:last-child {
  border-bottom: none;
}

.list > li + li {
  border-top: 1px solid #f0f0f0;
}

/* Table styling */
.table > thead > tr > th {
  background-color: #f8f9fa;
  font-weight: 600;
  padding: var(--combinator-gap-md);
  text-align: left;
  border-bottom: 2px solid #dee2e6;
}

.table > tbody > tr > td {
  padding: var(--combinator-gap-md);
  border-bottom: 1px solid #dee2e6;
}

.table > tbody > tr > td:first-child {
  font-weight: 500;
}

/* Accordion styling */
.accordion > .trigger {
  padding: var(--combinator-gap-md);
  background-color: #f8f9fa;
  border: 1px solid #dee2e6;
  cursor: pointer;
  user-select: none;
}

.accordion > .trigger + .panel {
  display: none;
  padding: var(--combinator-gap-md);
  border: 1px solid #dee2e6;
  border-top: none;
}

.accordion > .trigger.active + .panel {
  display: block;
}

/* Breadcrumb styling */
.breadcrumb > li {
  display: inline-block;
}

.breadcrumb > li + li::before {
  content: " / ";
  color: #666;
  margin: 0 var(--combinator-gap-sm);
}

.breadcrumb > li > a {
  color: #666;
  text-decoration: none;
}

.breadcrumb > li > a:hover {
  color: #007bff;
}

.breadcrumb > li:last-child > a {
  color: #333;
  font-weight: 500;
}

/* Form fieldsets */
.fieldset > legend {
  font-weight: 600;
  color: #333;
  margin-bottom: var(--combinator-gap-md);
}

.fieldset > .field {
  margin-bottom: var(--combinator-gap-md);
}

.fieldset > .field > label {
  display: block;
  margin-bottom: var(--combinator-gap-sm);
  font-weight: 500;
}

.fieldset > .field > input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

/* Responsive combinators */
@media (max-width: 768px) {
  .nav > ul > li {
    display: block;
    margin-bottom: var(--combinator-gap-sm);
  }
  
  .nav > ul > li > a {
    display: block;
    padding: var(--combinator-gap-md);
  }
  
  .card > .header {
    padding: var(--combinator-gap-md);
  }
  
  .card > .content {
    padding: var(--combinator-gap-md);
  }
  
  .card > .footer {
    padding: var(--combinator-gap-md);
  }
}

/* Accessibility improvements */
:focus-visible {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* High contrast mode */
@media (prefers-contrast: high) {
  .card,
  .form-field,
  .nav > ul > li > a {
    border: 2px solid #000;
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .nav > ul > li > a,
  .button,
  .accordion > .trigger {
    transition: none;
  }
}

Summary

CSS combinators provide powerful ways to select elements based on their relationships to other elements. Understanding combinators enables precise targeting without additional classes and creates more maintainable stylesheets.

Key Takeaways:

  • Descendant (space) selects all nested elements
  • Child (>) selects only direct children
  • Adjacent sibling (+) selects immediate following elements
  • General sibling (~) selects all following elements
  • :has() enables parent selection (modern browsers)
  • Consider performance with complex selectors
  • Document complex selector logic for maintainability

Remember: Combinators are about relationshipsβ€”understanding the document structure is key to using them effectively!

πŸ§ͺ Quick Quiz

Which combinator selects direct children?