Using the shadow DOM as a better iframe

Mailcoach can send beautiful newsletters to your audience. It can also display any sent newsletter in a beautiful archive that you can customize to your liking.

In this blog post, I’d like to explain how we ensure that the CSS of our webpage does not break the layout of your newsletter.

Welcome to the Shadow DOM

Let’s look at a detail page of a sent newsletter.

This page has two parts. At the top, we have the subscription form, and the newsletter’s content is shown at the bottom.

The newsletter is a complete HTML document, so we can’t just take a complete HTML document and use it in the main template. Even if we would only take the contents of the body part of the newsletter and inject that into the page, there would be a problem with the styling.

We want to ensure the web page’s styling is independent of how the newsletter is displayed and vice versa. It would be pretty bad if we showed the newsletter using the same yellow background color as the website, as the newsletter wasn’t sent with a yellow background.

These problems can be solved using the shadow DOM. Using the Shadow DOM, you can encapsulate a piece of HTML. What is rendered there isn’t affected by the other parts of your page and vice versa.

Let’s see how we use the shadow DOM in Mailcoach. Here’s a part of the HTML used to render that detail page above.

<nav class="back">
    <a href="{{ $emailList->websiteUrl() }}">
        <i>←</i> Back to overview
<div class="show">
    <article class="card">
        <header class="card-header">
            <time datetime="{{ $campaign->sent_at }}">{{ $campaign->sent_at->format('F j, Y') }}</time>
            <h2>{{ $campaign->subject }}</h2>
        <div class="webview">
           <x-mailcoach::web-view :html="$webview"/>

Notice that the web-view Blade component receives the $webview variable, which contains the entire HTML contents of a sent newsletter.

That web-view Blade component is backed by this view which contains the ✨ shadow DOM magic ✨.

    class EmbeddedWebview extends HTMLElement {
        connectedCallback() {
            const shadow = this.attachShadow({ mode: 'closed' });
            shadow.innerHTML = @json($html);

    window.customElements.define('embedded-webview', EmbeddedWebview);

<embedded-webview />

Let’s go through this code. In the script part, we first create a custom HTMLElement called EmbeddedWebview. The connectedCallback function will be called when our custom element is rendered. In the shadow variable, we’re referencing the shadow DOM of the element. And finally, we’re setting the HTML of the shadow DOM to the HTML of the newsletter (contained in $html).

Remember, the main benefit of using this shadow DOM over regular DOM is that the shadow DOM is wholly isolated from the regular DOM.

Lastly, we put our <embedded-webview /> element in the regular DOM of the page, which when it gets rendered, will execute the code in the script tag. You see that there isn’t too much code involved to this all.

Why not use an iframe

You might wonder why we didn’t use a simple iframe for this. The shadow DOM has significant benefits over an iframe for this use case.

First, the shadow DOM’s content follows the flow of the main HTML document. Custom HTML will grow with the content, and no scrollbars or strange scrolling behavior will be introduced.

The second benefit has to do with security. By setting the innerHTML of the shadow DOM, any JavaScript that is in the value of the innerHTML will not be executed. This is good to stop people from misusing Mailcoach to stage XSS attacks.

In closing

Using the shadow DOM, we can ensure that the sent newsletter we display is not affected by the layout of our website.

Mailcoach is the best service for sending out email campaigns. We also offer email automation that allows you to quickly build a drip campaign. You can manage your content and templates via powerful HTML and Markdown editors. Start your free trial now.

Ready to get started?