Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

Interactive examples architecture and the shadow DOM

In our interactive examples architecture, examples are built as complete separate pages, served from their own domain, like this: When we include them on MDN, we do it by creating an iframe in the MDN page, that contains the interactive examples page. So the interactive example is in its own isolated world, separate from the rest of the page.

This has some advantages. One is security: we don't have to worry much about what the example code is doing, because it can't get access to the rest of the page. So we don't really have to vet contributions much for security. Another is just isolation: if we do things in the interactive example they won't "escape" into the page. For example, in the JS interactive examples we redefine console.log() to make it write to the example's output pane, but we don't want that change to affect code running in the MDN page.

There are also some disadvantages to the iframe thing. One is that it's hard to set the size of the iframe, which means we need to have this clumsy mechanism where examples can only come in a range of fixed sizes. Another is performance: the browser loads iframe content after main page content, which makes it slow to load the example. When we first launched interactive examples, we were very worried about performance, and started a project to move the examples "inline", but it never went anywhere. Another problem is that it's hard to apply styles consistently across the main page and the example UI.

But overall I'd say that although the iframe thing is a hack, it's a hack that's served us pretty well for a few years.

But, the example page really consists of two parts:

  • the editor part, which includes all the UI (titles, labels, code editors)
  • the output part, which is where the result of the code is displayed

Currently both those parts are in the scope for the example code. You could see this, for example, by loading any JS interactive example, say, and replacing the example code with:

const title = document.querySelector("h4");
title.textContent = "hello";

...and you'll see that the editor's title has changed. So for the JS and the CSS examples, the code runs in the same scope as all the editor UI. This is OK for the JS and CSS examples: the JS examples are quite self-contained and with the CSS examples we constrain the CSS so it's only applied to a specific element.

But the HTML examples are more like full editing environments, where we want to have complete CSS rules that apply to whole sets of elements. For example, in the example at, we have some CSS like this:

p {
    font-weight: bold;

...but we don't want that to escape into the editor and style the editor UI's paragraphs as bold. So we needed a way to constrain the code only to the output pane of the editor. We currently do that using Shadow DOM. This means we put the output pane in a shadow DOM tree, and we apply the styles to that shadow DOM tree. Because of that, the CSS rules are constrained to the shadow DOM tree, and don't leak out into the interactive example document (mdn/interactive-examples#706).

That works fine for the HTML examples, and at first we thought we could use the same design for Web/API examples. But we can't, because most Web APIs aren't available in the shadow DOM context. For example, when we write a web page, many Web APIs are available on the Document object, which is accessible using the document global:

const header = document.querySelector('header');

To write Web API examples with the shadow DOM implementation of the editor, we expect to call these APIs on the "shadow root". You can see my efforts to do this in mdn/interactive-examples#1186: under the hood we need to do something like:

const documentRoot = getShadowRoot();
const header = documentRoot.querySelector('header');

The problem is, getShadowRoot() doesn't return a Document, it returns a ShadowRoot, which only has a few of the Document APIs on it.

So this, we think, completely kills the idea of using the Shadow DOM editor for Web APIs.

Another issue with Shadow DOM is that some CSS features don't work in it. In particular @font-face (mdn/interactive-examples#887, mdn/interactive-examples#1015) and apparently @counter-style as well (mdn/interactive-examples#2089) and maybe some other at-rules too, I don't know.

And that's where things have been, until NiedziolkaMichal came along and tried the approach of using another iframe for the output pane, so we have (I guess?) one iframe for the whole example, including the editor, and that embeds another iframe, for the output of the example. That means code running in the output is isolated from the editor, as it is with shadow DOM, but it also gets a complete web page environment, with a Document and everything else.

I'm excited about this because I think having IEs for basic Web APIs would be a big upgrade for MDN. But I'm not a maintainer of the IE machinery, so it's really a decision of the dev team how they want to go ahead with this.

I also think this is not the perfect architecture: it would probably be better to have the editor code inline in the page (i.e. not in an iframe at all). Another option, which we explored a little, would be to use a third-party editor like an embedded CodePen. But the fact that NiedziolkaMichal has an implementation of the editor that works, today, seems like a big point in its favour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment