10 Practical Use Cases for :has() CSS Relational Pseudo-Class

The CSS :has() pseudo-class is a game-changer. This powerful relational pseudo-class lets you select elements based on what they contain. Think of it as “parent selection” – something CSS developers have wanted for years. In this guide, you’ll discover 10 practical ways to use :has() in your web development projects. From simple form styling to complex layout patterns, these examples will transform how you write CSS. Let’s dive in.

CSS has selector: 10 use cases to remember

What is the :has() Pseudo-Class?

The :has() relational pseudo-class selects elements that contain specific child elements or match certain conditions.

Here’s the basic syntax:

parent:has(child) {
  /* styles for parent when it contains child */
}

Browser support is excellent across modern browsers (Chrome 105+, Firefox 121+, Safari 15.4+).

Now let’s explore real-world use cases.

1. Style Forms Based on Input Status (Basic)

Forms become more user-friendly when they provide visual feedback. The :has() pseudo-class makes this incredibly simple.

View on Codepen

HTML:

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" id="email" required>
</div>

<div class="form-group">
  <label for="name">Name</label>
  <input type="text" id="name" value="John Doe">
</div>

CSS:

.form-group {
  padding: 1rem;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  transition: all 0.3s ease;
}

/* Style form groups with focused inputs */
.form-group:has(input:focus) {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

/* Style form groups with filled inputs */
.form-group:has(input:not(:placeholder-shown)) {
  background-color: #f0f9ff;
}

/* Style form groups with invalid inputs */
.form-group:has(input:invalid) {
  border-color: #ef4444;
  background-color: #fef2f2;
}

This approach eliminates the need for JavaScript to track input states. The parent container automatically updates its appearance based on the input’s condition.

2. Create Dynamic Card Layouts (Basic)

Cards with different content types need different styling. The :has() pseudo-class lets you create adaptive card designs.

View on Codepen

HTML:

<div class="card">
  <img src="https://picsum.photos/id/21/6002/502" alt="Product">
  <h3>Product Title</h3>
  <p>Product description goes here.</p>
</div>

<div class="card">
  <h3>Text Only Card</h3>
  <p>This card has no image.</p>
</div>

CSS:

.card {
  padding: 1.5rem;
  border-radius: 12px;
  background: white;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s ease;
}

/* Cards with images get different padding */
.card:has(img) {
  padding: 0;
  overflow: hidden;
}

.card:has(img) h3,
.card:has(img) p {
  padding: 0 1.5rem;
  margin: 1rem 1.5rem;
}

/* Text-only cards get centered alignment */
.card:not(:has(img)) {
  text-align: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

Your cards now adapt automatically based on their content structure.

3. Build Smart Navigation Menus (Intermediate)

Navigation menus often need different behaviors for items with and without submenus. The :has() pseudo-class makes this elegant.

View on Codepen

HTML:

<nav class="main-nav">
  <ul>
    <li><a href="/">Home</a></li>
    <li class="dropdown">
      <a href="/products">Products</a>
      <ul class="submenu">
        <li><a href="/products/web">Web</a></li>
        <li><a href="/products/mobile">Mobile</a></li>
      </ul>
    </li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

CSS:

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

.main-nav li {
  position: relative;
}

.main-nav a {
  display: block;
  padding: 1rem 1.5rem;
  text-decoration: none;
  color: #374151;
  transition: all 0.3s ease;
}

/* Style parent items that have submenus */
.main-nav li:has(.submenu) > a {
  padding-right: 2.5rem;
  position: relative;
}

/* Add dropdown arrow to items with submenus */
.main-nav li:has(.submenu) > a::after {
  content: '▼';
  position: absolute;
  right: 1rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.8rem;
}

/* Show submenu on hover */
.main-nav li:has(.submenu):hover .submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.submenu {
  position: absolute;
  top: 100%;
  left: 0;
  background: white;
  min-width: 200px;
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: all 0.3s ease;
}

Menu items automatically get dropdown indicators and behaviours when they contain submenus.

4. Implement Toggle Functionality (Intermediate)

Create collapsible sections without JavaScript using the :has() pseudo-class with checkboxes.

View on Codepen

HTML:

<div class="accordion-item">
  <input type="checkbox" id="section1" class="toggle">
  <label for="section1" class="accordion-header">
    <h3>Frequently Asked Questions</h3>
    <span class="icon">+</span>
  </label>
  <div class="accordion-content">
    <p>This is the expandable content section. It can contain any HTML content you need.</p>
  </div>
</div>

CSS:

.accordion-item {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  margin-bottom: 1rem;
  overflow: hidden;
}

.toggle {
  display: none;
}

.accordion-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.5rem;
  cursor: pointer;
  background: #f9fafb;
  transition: background-color 0.3s ease;
}

.accordion-header:hover {
  background: #f3f4f6;
}

.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
}

/* Expand content when checkbox is checked */
.accordion-item:has(.toggle:checked) .accordion-content {
  max-height: 500px;
  padding: 1.5rem;
}

/* Rotate icon when expanded */
.accordion-item:has(.toggle:checked) .icon {
  transform: rotate(45deg);
}

.icon {
  font-size: 1.5rem;
  font-weight: bold;
  transition: transform 0.3s ease;
}

This creates fully functional accordion components with smooth animations and no JavaScript required.

5. Build Advanced Data Tables (Intermediate)

Data tables become more interactive when rows respond to their content. The :has() pseudo-class enables sophisticated table styling.

View on Codepen

HTML:

<table class="data-table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Status</th>
      <th>Priority</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Project Alpha</td>
      <td><span class="status active">Active</span></td>
      <td><span class="priority high">High</span></td>
      <td><button>Edit</button></td>
    </tr>
    <tr>
      <td>Project Beta</td>
      <td><span class="status inactive">Inactive</span></td>
      <td><span class="priority low">Low</span></td>
      <td><button>Edit</button></td>
    </tr>
  </tbody>
</table>

CSS:

.data-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.data-table th,
.data-table td {
  padding: 1rem;
  text-align: left;
  border-bottom: 1px solid #e5e7eb;
}

/* Style rows with high priority items */
.data-table tr:has(.priority.high) {
  background: linear-gradient(90deg, #fef2f2 0%, white 20%);
  border-left: 4px solid #ef4444;
}

/* Style rows with inactive status */
.data-table tr:has(.status.inactive) {
  opacity: 0.6;
  background: #f9fafb;
}

/* Highlight rows on hover, but only if they're active */
.data-table tr:not(:has(.status.inactive)):hover {
  background: #f0f9ff;
  transform: scale(1.01);
  transition: all 0.2s ease;
}

/* Style rows that have buttons */
.data-table tr:has(button) td:last-child {
  text-align: center;
}

.status, .priority {
  padding: 0.25rem 0.75rem;
  border-radius: 9999px;
  font-size: 0.875rem;
  font-weight: 500;
}

.status.active { background: #dcfce7; color: #166534; }
.status.inactive { background: #f3f4f6; color: #6b7280; }
.priority.high { background: #fee2e2; color: #dc2626; }
.priority.low { background: #f0f9ff; color: #2563eb; }

Tables now provide visual context based on their data, making important information immediately recognizable.

6. Create Context-Aware Sidebars (Advanced)

Sidebars that adapt based on their content create better user experiences. This advanced technique uses :has() for dynamic layouts.

View on Codepen

HTML:

<div class="layout">
  <aside class="sidebar">
    <div class="widget">
      <h3>Recent Posts</h3>
      <ul>
        <li><a href="#">Post 1</a></li>
        <li><a href="#">Post 2</a></li>
      </ul>
    </div>
    
    <div class="widget ad">
      <h3>Advertisement</h3>
      <div class="ad-content">
        <img src="https://picsum.photos/id/39/300/250" alt="Ad">
      </div>
    </div>
  </aside>
  
  <main class="content">
    <h1>Main Content</h1>
    <p>Your main content goes here.</p>
  </main>
</div>

CSS:

.layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 2rem;
  max-width: 1200px;
  margin: 0 auto;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* Adjust layout when sidebar has ads */
.layout:has(.sidebar .ad) {
  grid-template-columns: 350px 1fr;
}

/* Style widgets differently based on content */
.widget {
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Ad widgets get special styling */
.widget:has(.ad-content) {
  background: linear-gradient(135deg, #fef3c7 0%, #f59e0b 100%);
  border: 2px solid #d97706;
  text-align: center;
}

/* Widgets with lists get compact styling */
.widget:has(ul) {
  padding: 1rem;
}

.widget:has(ul) h3 {
  margin-bottom: 0.75rem;
  font-size: 1rem;
}

/* Responsive behavior */
@media (max-width: 768px) {
  .layout {
    grid-template-columns: 1fr;
  }
  
  /* Stack sidebar horizontally on mobile if it has ads */
  .layout:has(.sidebar .ad) .sidebar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }

.ad-content img {
max-width: 100%;
height: auto;
}
}

The layout automatically adjusts its grid structure and widget styling based on sidebar content.

7. Implement Smart Image Galleries (Advanced)

Photo galleries need different layouts for different aspect ratios and content types. The :has() pseudo-class creates intelligent gallery systems.

View on Codepen

HTML:

<div class="gallery">
  <div class="gallery-item">
    <img src="https://picsum.photos/800/450" alt="Landscape" class="landscape">
    <div class="caption">Beautiful landscape view</div>
  </div>
  
  <div class="gallery-item">
    <img src="https://picsum.photos/450/800" alt="Portrait" class="portrait">
    <div class="caption">Portrait photography</div>
  </div>
  
  <div class="gallery-item">
    <img src="https://picsum.photos/400/400" alt="Square" class="square">
  </div>
</div>

CSS:

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  padding: 1rem;
}

.gallery-item {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
}

/* Items with landscape images */
.gallery-item:has(.landscape) {
  grid-column: span 2;
  aspect-ratio: 16/9;
}

/* Items with portrait images */
.gallery-item:has(.portrait) {
  aspect-ratio: 3/4;
  grid-row: span 2;
}

/* Items with square images */
.gallery-item:has(.square) {
  aspect-ratio: 1;
}

/* Items without captions get centered content */
.gallery-item:not(:has(.caption)) {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Items with captions get overlay styling */
.gallery-item:has(.caption) {
  position: relative;
}

.gallery-item:has(.caption)::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 50%;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
  pointer-events: none;
}

.caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 1.5rem;
  color: white;
  font-weight: 500;
  z-index: 1;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery-item:hover {
  transform: translateY(-5px);
}

The gallery automatically creates optimal layouts based on image types and the presence of captions.

8. Build Complex Form Validation UI (Advanced)

Advanced forms need sophisticated validation feedback. The :has() pseudo-class enables elegant form validation without JavaScript.

View on Codepen

HTML:

<form class="validation-form">
  <div class="field-group">
    <label for="username">Username</label>
    <input type="text" id="username" required minlength="3">
    <div class="validation-message">Username must be at least 3 characters</div>
  </div>
  
  <div class="field-group">
    <label for="email">Email</label>
    <input type="email" id="email" required>
    <div class="validation-message">Please enter a valid email address</div>
  </div>
  
  <div class="field-group checkbox-group">
    <input type="checkbox" id="terms" required>
    <label for="terms">I agree to the terms and conditions</label>
    <div class="validation-message">You must agree to the terms</div>
  </div>
  
  <button type="submit">Create Account</button>
</form>

CSS:

.validation-form {
  max-width: 500px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border-radius: 16px;
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}

.field-group {
  margin-bottom: 1.5rem;
  position: relative;
}

label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: #374151;
}

input[type="text"],
input[type="email"] {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 1rem;
  transition: all 0.3s ease;
}

/* Field groups with valid inputs */
.field-group:has(input:valid:not(:placeholder-shown)) {
  position: relative;
}

.field-group:has(input:valid:not(:placeholder-shown))::after {
  content: '✓';
  position: absolute;
  right: 12px;
  top: 38px;
  color: #10b981;
  font-weight: bold;
}

/* Field groups with invalid inputs */
.field-group:has(input:invalid:not(:placeholder-shown)) input {
  border-color: #ef4444;
  background: #fef2f2;
}

.field-group:has(input:invalid:not(:placeholder-shown)) .validation-message {
  display: block;
  color: #ef4444;
  font-size: 0.875rem;
  margin-top: 0.5rem;
}

.validation-message {
  display: none;
}

/* Checkbox specific styling */
.checkbox-group {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

.checkbox-group label {
  margin: 0;
  flex: 1;
}

.checkbox-group input[type="checkbox"] {
  margin-top: 0.25rem;
}

/* Show validation for unchecked required checkboxes */
.field-group:has(input[type="checkbox"]:invalid) .validation-message {
  display: block;
  color: #ef4444;
  font-size: 0.875rem;
  margin-top: 0.5rem;
  grid-column: 1 / -1;
}

/* Submit button styling */
button[type="submit"] {
  width: 100%;
  padding: 1rem;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

button[type="submit"]:hover {
  background: #2563eb;
}

/* Disable submit button if form has invalid fields */
.validation-form:has(input:invalid) button[type="submit"] {
  background: #9ca3af;
  cursor: not-allowed;
}

This creates a fully functional form validation system that responds to user input in real-time.

9. Design Adaptive Content Sections (Advanced)

Content sections that adapt based on their media and text content create more engaging layouts.

View on Codepen

HTML:

<section class="content-section">
  <div class="media">
    <video controls>
      <source src="demo.mp4" type="video/mp4">
    </video>
  </div>
  <div class="text-content">
    <h2>Video Tutorial</h2>
    <p>Watch this comprehensive tutorial to learn advanced techniques.</p>
    <a href="#" class="cta-button">Get Started</a>
  </div>
</section>

<section class="content-section">
  <div class="media">
    <img src="https://picsum.photos/id/175/300/400" alt="Data visualization">
  </div>
  <div class="text-content">
    <h2>Data Insights</h2>
    <p>Our latest research reveals fascinating trends in user behavior.</p>
  </div>
</section>

<section class="content-section">
  <div class="text-content">
    <h2>Pure Text Section</h2>
    <p>Sometimes the most powerful content needs no visual support. This section demonstrates pure text content.</p>
    <blockquote>"The best designs are invisible." - Anonymous</blockquote>
  </div>
</section>

CSS:

.content-section {
  display: grid;
  gap: 2rem;
  margin-bottom: 3rem;
  padding: 2rem;
  background: white;
  border-radius: 16px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  transition: transform 0.3s ease;
}

/* Sections with video content */
.content-section:has(video) {
  grid-template-columns: 1.2fr 1fr;
  background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
  color: white;
}

.content-section:has(video) .cta-button {
  background: #f59e0b;
  color: #1e293b;
}

/* Sections with images */
.content-section:has(img) {
  grid-template-columns: 1fr 1fr;
  align-items: center;
}

/* Text-only sections */
.content-section:not(:has(.media)) {
  grid-template-columns: 1fr;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.content-section:not(:has(.media)) h2 {
  font-size: 2.5rem;
  margin-bottom: 1.5rem;
}

/* Sections with call-to-action buttons */
.content-section:has(.cta-button) {
  position: relative;
  overflow: hidden;
}

.content-section:has(.cta-button)::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
  transition: left 0.5s ease;
}

.content-section:has(.cta-button):hover::before {
  left: 100%;
}

/* Media styling */
.media {
  border-radius: 12px;
  overflow: hidden;
}

.media img,
.media video {
  width: 100%;
  height: auto;
  display: block;
}

/* Content styling */
.text-content h2 {
  font-size: 2rem;
  margin-bottom: 1rem;
  color: inherit;
}

.text-content p {
  font-size: 1.1rem;
  line-height: 1.6;
  margin-bottom: 1.5rem;
  opacity: 0.9;
}

.cta-button {
  display: inline-block;
  padding: 1rem 2rem;
  background: #3b82f6;
  color: white;
  text-decoration: none;
  border-radius: 50px;
  font-weight: 500;
  transition: all 0.3s ease;
}

.cta-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3);
}

blockquote {
  font-style: italic;
  font-size: 1.25rem;
  border-left: 4px solid #3b82f6;
  padding-left: 1.5rem;
  margin: 2rem 0;
  color: #6b7280;
}

/* Responsive design */
@media (max-width: 768px) {
  .content-section:has(video),
  .content-section:has(img) {
    grid-template-columns: 1fr;
  }
  
  .content-section:has(video) .media {
    order: -1;
  }
}

Content sections automatically adapt their layout, styling, and behavior based on the media types they contain.

10. Create Interactive Dashboard Widgets (Expert)

Dashboard widgets that respond to their data and state create powerful admin interfaces. This expert-level example shows the full potential of :has().

View on Codepen

HTML:

<div class="dashboard">
  <div class="widget" data-status="healthy">
    <div class="widget-header">
      <h3>Server Status</h3>
      <div class="status-indicator healthy"></div>
    </div>
    <div class="widget-content">
      <div class="metric">
        <span class="value">99.9%</span>
        <span class="label">Uptime</span>
      </div>
      <div class="trend positive">
        <span class="arrow">↗</span>
        <span>+0.1%</span>
      </div>
    </div>
  </div>
  
  <div class="widget" data-status="warning">
    <div class="widget-header">
      <h3>Memory Usage</h3>
      <div class="status-indicator warning"></div>
    </div>
    <div class="widget-content">
      <div class="metric">
        <span class="value">87%</span>
        <span class="label">Used</span>
      </div>
      <div class="trend negative">
        <span class="arrow">↘</span>
        <span>+5%</span>
      </div>
      <div class="alert">
        <span class="alert-icon">⚠</span>
        <span>High usage detected</span>
      </div>
    </div>
  </div>
  
  <div class="widget" data-status="critical">
    <div class="widget-header">
      <h3>Disk Space</h3>
      <div class="status-indicator critical"></div>
    </div>
    <div class="widget-content">
      <div class="metric">
        <span class="value">95%</span>
        <span class="label">Full</span>
      </div>
      <div class="trend negative">
        <span class="arrow">↘</span>
        <span>+12%</span>
      </div>
      <div class="alert critical">
        <span class="alert-icon">🚨</span>
        <span>Immediate action required</span>
      </div>
      <button class="action-button">Clear Cache</button>
    </div>
  </div>
</div>

CSS:

.dashboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
  padding: 2rem;
  background: #f8fafc;
  min-height: 100vh;
}

.widget {
  background: white;
  border-radius: 16px;
  padding: 1.5rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
  transition: all 0.3s ease;
  border: 2px solid transparent;
}

/* Widget status-based styling */
.widget:has(.status-indicator.healthy) {
  border-color: #10b981;
  background: linear-gradient(135deg, white 0%, #f0fdf4 100%);
}

.widget:has(.status-indicator.warning) {
  border-color: #f59e0b;
  background: linear-gradient(135deg, white 0%, #fffbeb 100%);
  animation: pulse-warning 2s infinite;
}

.widget:has(.status-indicator.critical) {
  border-color: #ef4444;
  background: linear-gradient(135deg, white 0%, #fef2f2 100%);
  animation: pulse-critical 1s infinite;
}

@keyframes pulse-warning {
  0%, 100% { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); }
  50% { box-shadow: 0 4px 20px rgba(245, 158, 11, 0.3); }
}

@keyframes pulse-critical {
  0%, 100% { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); }
  50% { box-shadow: 0 4px 20px rgba(239, 68, 68, 0.4); }
}

/* Widgets with alerts get expanded layout */
.widget:has(.alert) {
  grid-row: span 2;
}

.widget:has(.alert) .widget-content {
  display: grid;
  gap: 1rem;
  grid-template-rows: auto auto 1fr;
}

/* Widgets with action buttons */
.widget:has(.action-button) {
  position: relative;
  overflow: hidden;
}

.widget:has(.action-button)::after {
  content: '';
  position: absolute;
  top: 0;
  right: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(239, 68, 68, 0.1), transparent);
  animation: urgent-sweep 3s infinite;
}

@keyframes urgent-sweep {
  0% { right: -100%; }
  50% { right: 100%; }
  100% { right: 100%; }
}

/* Widget header */
.widget-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid #e5e7eb;
}

.widget-header h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: #1f2937;
}

/* Status indicators */
.status-indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  position: relative;
}

.status-indicator::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  opacity: 0.3;
  animation: status-ping 2s infinite;
}

.status-indicator.healthy {
  background: #10b981;
}

.status-indicator.healthy::after {
  background: #10b981;
}

.status-indicator.warning {
  background: #f59e0b;
}

.status-indicator.warning::after {
  background: #f59e0b;
}

.status-indicator.critical {
  background: #ef4444;
}

.status-indicator.critical::after {
  background: #ef4444;
}

@keyframes status-ping {
  0% { transform: scale(0.8); opacity: 0.3; }
  50% { transform: scale(1.2); opacity: 0.1; }
  100% { transform: scale(0.8); opacity: 0.3; }
}

/* Widget content */
.widget-content {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.metric {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.metric .value {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1;
  margin-bottom: 0.5rem;
}

.metric .label {
  font-size: 0.875rem;
  color: #6b7280;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Trends */
.trend {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: 50px;
  font-size: 0.875rem;
  font-weight: 500;
}

.trend.positive {
  background: #dcfce7;
  color: #166534;
}

.trend.negative {
  background: #fee2e2;
  color: #dc2626;
}

.trend .arrow {
  font-size: 1.25rem;
}

/* Alerts */
.alert {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem;
  border-radius: 12px;
  font-size: 0.875rem;
  font-weight: 500;
  margin-top: auto;
}

.alert:not(.critical) {
  background: #fef3c7;
  color: #92400e;
  border: 1px solid #f59e0b;
}

.alert.critical {
  background: #fee2e2;
  color: #991b1b;
  border: 1px solid #ef4444;
  animation: alert-glow 2s infinite;
}

@keyframes alert-glow {
  0%, 100% { box-shadow: 0 0 5px rgba(239, 68, 68, 0.3); }
  50% { box-shadow: 0 0 20px rgba(239, 68, 68, 0.6); }
}

.alert-icon {
  font-size: 1.25rem;
}

/* Action buttons */
.action-button {
  margin-top: 1rem;
  padding: 0.75rem 1.5rem;
  background: #ef4444;
  color: white;
  border: none;
  border-radius: 8px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
  z-index: 1;
}

.action-button:hover {
  background: #dc2626;
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4);
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    padding: 1rem;
  }
  
  .widget:has(.alert) {
    grid-row: span 1;
  }
}

This expert-level dashboard demonstrates how :has() can create sophisticated, responsive interfaces that react to data states and content types.

Browser Support and Performance Considerations

The :has() pseudo-class enjoys excellent support in modern browsers:

  • Chrome 105+ (September 2022)
  • Firefox 121+ (December 2023)
  • Safari 15.4+ (March 2022)

Performance Tips:

  1. Avoid deep nesting: Keep :has() selectors as simple as possible
  2. Use specific selectors: Don’t rely on overly broad matching
  3. Test on lower-end devices: Complex :has() rules can impact performance
  4. Consider fallbacks: Provide basic styling for older browsers

Wrapping Up

The :has() pseudo-class transforms how we approach CSS architecture. Instead of adding classes with JavaScript or creating complex markup structures, you can now create intelligent styles that respond to content automatically.

Key takeaways:

  • Start with simple use cases like form styling and card layouts
  • Progress to intermediate techniques like navigation menus and data tables
  • Master advanced patterns for galleries, dashboards, and adaptive layouts
  • Always consider browser support and performance implications
  • Test thoroughly across different devices and browsers

The examples in this guide provide a solid foundation for incorporating :has() into your web development workflow. As browser support continues to improve, this powerful pseudo-class will become an essential tool for creating responsive, intelligent user interfaces.

What will you build with :has() next?

Share

Facebook
Twitter
Pinterest
LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *