---
title: "ShadowDOM Widget Integration Tutorial"
canonical_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/examples/example-shadowdom-mutationobserver"
markdown_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/examples/example-shadowdom-mutationobserver.md"
last_updated: "2026-03-23T13:02:40Z"
---

# ShadowDOM Widget Integration Tutorial

## Intended Audience

This tutorial is for:

- **Advanced frontend developers** working with Web Components and ShadowDOM
- **Technical integrators** building encapsulated component architectures
- Developers familiar with JavaScript DOM manipulation and the ShadowDOM API
- Those who have completed basic [widget integration](https://apidocs.sportradar.com/resources/widgets/docs/tutorials.md) and need ShadowDOM support

## Goals

By completing this tutorial, you will:

- Understand how to integrate widgets inside ShadowDOM containers
- Learn to pass DOM element references instead of CSS selectors
- Implement a MutationObserver to clone widget styles into ShadowDOM
- Create a complete, working ShadowDOM widget integration
- Handle both open and closed ShadowDOM modes

## Prerequisites

Before starting this tutorial, ensure you have:

- **Valid Sportradar Widget license** - Contact [Sales](mailto:sales@betradar.com) if needed
- **Client ID** - Provided with your license
- **JavaScript knowledge** - Understanding of:
  - DOM manipulation
  - ShadowDOM API (`attachShadow`, shadow roots)
  - MutationObserver API
  - ES6+ syntax
- **Basic widget integration experience** - Completed [Getting Started Guide](https://apidocs.sportradar.com/resources/widgets/docs/tutorials.md)
- **Web development environment** - Text editor and modern browser with ShadowDOM support (Chrome, Firefox, Safari, or Edge)
- **Match ID** - A valid match identifier for testing

> **Info**
>
> ShadowDOM is supported in all modern browsers. Legacy browsers (IE11) do not support ShadowDOM and require polyfills.

***

## Overview

ShadowDOM provides style and DOM encapsulation for web components, but this encapsulation presents two challenges for widget integration:

1. **CSS selectors don't work** - Widget container inside ShadowDOM cannot be targeted with standard selector strings, so direct DOM element must be passed instead
2. **Styles are isolated** - Widget styles injected into `<head>` are not visible inside ShadowDOM, so they need to be cloned into the ShadowDOM.

This tutorial shows you how to overcome both challenges.

***

## Tutorial Steps

### Step 1: Create ShadowDOM Container

First, create a host element and attach a shadow root to it. Then create a container element inside the shadow root where the widget will be rendered.

```html
<div id="widget-host"></div>
```

```js {3-5}
const host = document.getElementById('widget-host');
const shadowRoot = host.attachShadow({ mode: 'open' });
const widgetContainer = document.createElement('div');
shadowRoot.appendChild(widgetContainer);
```

> **Tip**
>
> Both `{ mode: 'open' }` and `{ mode: 'closed' }` work with this approach. The widget framework doesn't need access to the shadow root after initialization.

### Step 2: Pass DOM Element Reference to Widget API

When working with ShadowDOM, you cannot use CSS selector strings with `SIR('addWidget')`. Instead, pass a direct reference to the container DOM element.

> **Warning**
>
> ❌ **This will NOT work:**
>
> ```js
> SIR('addWidget', '#widget-container', 'match.lmtPlus', { ... });
> ```
>
> ✅ **This WILL work:**
>
> ```js
> SIR('addWidget', widgetContainer, 'match.lmtPlus', { ... });
> ```

**Example initialization:**

```js {8}
// Include widgetloader
(function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
)})(window,document,"script","https://widgets.sir.sportradar.com/[CLIENT_ID]/widgetloader","SIR",{
    theme: 'default',
    language: 'en'
});

SIR('addWidget', widgetContainer, 'match.lmtPlus', {
    matchId: 'your_match_id_here'
});
```

> **Note**
>
> Replace `[CLIENT_ID]` with your actual client ID and `your_match_id_here` with a valid match identifier.

### Step 3: Set up Style Container

Create a dedicated container inside the ShadowDOM to hold the cloned `<link>` elements.

```js {2-3}
const widgetContainer = document.createElement('div');
const linkContainer = document.createElement('div');
shadowRoot.appendChild(linkContainer);
shadowRoot.appendChild(widgetContainer);
```

> **Info**
>
> The `linkContainer` should be added before the `widgetContainer` to ensure styles are loaded in the correct order.

### Step 4: Implement MutationObserver for Style Cloning

Widgets inject `<link>` and `<style>` elements into the document `<head>`. These styles are not visible inside ShadowDOM by default. Use a MutationObserver to detect and clone these elements.

#### Understanding What to Clone

Only clone widget-specific styles to avoid conflicts:

- **`<link>` elements** with `href` containing `widgets.sir.sportradar.com`
- **`<style>` element** with `id="sr-client-theme"` (only when using custom themes)

```js {1-20}
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        mutation.addedNodes.forEach(node => {
            // Clone widget stylesheet links
            if (node.tagName === 'LINK' &&
                node.rel === 'stylesheet' &&
                node.href.includes('https://widgets.sir.sportradar.com')) {
                linkContainer.appendChild(node.cloneNode());
            }

            // Clone client theme styles (only if using custom theme)
            if (node.tagName === "STYLE" && node.id === "sr-client-theme") {
                const clonedNode = node.cloneNode(true);
                clonedNode.id = "sr-client-theme-clone";
                // Append after linkContainer to maintain style precedence
                shadowRoot.appendChild(clonedNode);
            }
        });
    });
});

observer.observe(document.head, { childList: true });
```

> **Warning**
>
> **Critical:** Set up the MutationObserver **before** calling the widgetloader to ensure all styles are captured during widget initialization.

### Step 5: Initialize Widget

Now that the observer is in place, initialize the widget as shown in Step 2. The observer will automatically clone styles as they are added to the document.

```js
SIR('addWidget', widgetContainer, 'match.lmtPlus', {
    matchId: 'your_match_id_here'
});
```

***

## Complete Implementation

**View Complete Working Example**

Here's the full implementation combining all steps:

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ShadowDOM Widget Integration</title>
</head>
<body>
    <div id="widget-host"></div>

    <script>
        // Step 1: Create ShadowDOM container
        const host = document.getElementById('widget-host');
        const shadowRoot = host.attachShadow({ mode: 'open' });

        // Step 3: Create containers for styles and widget
        const linkContainer = document.createElement('div');
        const widgetContainer = document.createElement('div');
        shadowRoot.appendChild(linkContainer);
        shadowRoot.appendChild(widgetContainer);

        // Step 4: Set up MutationObserver (BEFORE widgetloader)
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    // Clone widget stylesheet links
                    if (node.tagName === 'LINK' &&
                        node.rel === 'stylesheet' &&
                        node.href.includes('https://widgets.sir.sportradar.com')) {
                        linkContainer.appendChild(node.cloneNode());
                    }

                    // Clone client theme styles
                    if (node.tagName === "STYLE" && node.id === "sr-client-theme") {
                        const clonedNode = node.cloneNode(true);
                        clonedNode.id = "sr-client-theme-clone";
                        shadowRoot.appendChild(clonedNode);
                    }
                });
            });
        });

        observer.observe(document.head, { childList: true });

        // Step 2 & 5: Initialize widgetloader and add widget
        (function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
        g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
        )})(window,document,"script","https://widgets.sir.sportradar.com/[CLIENT_ID]/widgetloader","SIR",{
            theme: 'default',
            language: 'en'
        });

        // Pass DOM element reference (not selector string)
        SIR('addWidget', widgetContainer, 'match.lmtPlus', {
            matchId: 'your_match_id_here'
        });
    </script>
</body>
</html>
```

> **Note**
>
> Replace `[CLIENT_ID]` with your actual client ID and `your_match_id_here` with a valid match identifier.

***

## Key Takeaways

### DOM Element References

Always pass a direct DOM element reference to `SIR('addWidget')` when using ShadowDOM. CSS selector strings cannot penetrate shadow boundaries.

### Style Cloning

Use MutationObserver to clone widget-specific `<link>` and `<style>` elements from `<head>` into the ShadowDOM container.

### Order Matters

Set up the MutationObserver before loading the widgetloader script to capture all style elements during initialization.

***

## Further Reading

### Sportradar Widget Documentation

- [Getting Started Guide](https://apidocs.sportradar.com/resources/widgets/docs/tutorials.md) - Basic widget integration
- [Global SIR API](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/SIR.md) - Complete API reference
- [Widget Customization](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/widgets-customization.md) - Theming and styling options

### Web Platform APIs

- [ShadowDOM API (MDN)](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) - Comprehensive guide to ShadowDOM
- [MutationObserver API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) - Observing DOM changes
- [Web Components (MDN)](https://developer.mozilla.org/en-US/docs/Web/Web_Components) - Building encapsulated components
