
Shadow DOM
Working Across Boundaries
Shadow DOM provides built in encapsulation for styles and DOM structures, which is what makes it great for building UI components that work the same regardless of environment. While this encapsulation creates modular, reusable components, it also introduces a challenge: how do you work with elements across shadow boundaries?
Query’s $$
solves this by recursively piercing shadow DOM and slot roots to look for a selector.
Understanding the Challenge
Web components can often be deeply nested in complex applications, with multiple shadow DOM trees.
document└── ui-menu (shadow root) └── .items └── ui-button (shadow root) └── .label
Native APIs can’t see past the first shadow boundary, making it nearly impossible to access deeply nested elements without complex, brittle code.
// Standard DOM query stops at shadow boundariesconst button = document.querySelector('ui-button .label'); // Returns null
// The element exists but is hidden inside shadow DOM
The $$ Solution
The $$
function provides a unified way to query elements regardless of shadow boundaries:
import { $$ } from '@semantic-ui/query';
// Query crosses shadow boundariesconst buttonLabels = $$('ui-menu .items ui-button .label');
How It Works
Under the hood, $$
performs a recursive search through:
- Regular DOM elements
- Shadow roots of web components
- Slotted content elements
Real-World Examples
Finding Nested UI Elements
Access elements inside UI components:
// Find all buttons inside components$$('ui-menu ui-button') .addClass('discovered') .on('click', function() { console.log('Clicked button:', $(this).text()); });
Working with Slotted Content
Access content that’s been slotted into a component:
// From outside the component$$('ui-card slot .title').text('Updated Card Title');
// From inside the component's methodfunction updateSlottedContent() { // From within a component's method (using this.shadowRoot as context) $$('slot .content', { root: this.shadowRoot }) .addClass('processed');}
Navigating Component Hierarchies
Move through component trees:
// Find items in a menu structure$$('ui-menu .item.active') .forEach(item => { console.log('Active menu item:', item.textContent); });
Shadow DOM Example
This interactive example demonstrates the difference between $
and $$
when working with web components:
When to Use $$
The $$
function is powerful but comes with performance considerations:
// Fast but limited to regular DOM$('button.primary');
// More powerful but requires more processing$$('ui-button .label');
Use $$
when:
-
You need to access elements inside web components
$$('ui-modal .close-button').on('click', closeModal); -
Working with nested component structures
$$('ui-menu ui-button.active').css('font-weight', 'bold'); -
Accessing elements distributed via slots
$$('ui-card slot p').addClass('card-paragraph'); -
When you’re unsure about the DOM structure
// This works regardless of shadow DOM boundaries$$('.important-element');
Performance Optimization
When performance is critical:
-
Be specific with your selectors to minimize the search space
// Better performance with more specific selector$$('ui-menu.main .item'); -
Cache results when querying the same elements repeatedly
// Store for reuseconst $mainMenu = $$('ui-menu.main');// Reuse the cached result$mainMenu.find('.item').addClass('styled');$mainMenu.find('.item.active').addClass('highlighted'); -
Use
$
when possible if you know you’re not crossing shadow boundaries// Use $ for regular DOM when you can$('.sidebar').addClass('expanded');// Use $$ only when needed$$('ui-button .label');
Next Steps
Now that you understand how to work with Shadow DOM using $$
, explore these related topics: