HTML Demo

<html-demo> Mature JS

An element for displaying HTML content alongside its source code. Great for documenting web components!

Features

Roadmap

From most to least likely to be implemented:

Examples

Basic

Code-first:

<html-demo>
	<pre class="language-html"><code>
		&lt;input type=range>
	</code></pre>
</html-demo>

Content-first:

<html-demo id=foo>
	<input type=range>
</html-demo>

Adjusters

Only font-size for now:

<html-demo adjust="font-size">
	<button>Click me</button>
</html-demo>

Use --font-size-min and --font-size-max to set the range (default: 50% to 300%).

Toolbar buttons

The buttons attribute adds buttons to the inline end of the toolbar. These are built in:

Name Description
open Open the demo in a new tab, as a standalone document
codepen Open the demo in CodePen
reload Reload the demo. Only rendered for iframe demos
<html-demo isolate="iframe" buttons="open codepen reload">
	<label><input type="checkbox"> Check me, then reload</label>
</html-demo>

An empty buttons attribute adds every button available. Opened demos get a <base> pointing to the current page, so relative URLs keep working. Icons are masks, so they take the button’s text color. To swap one, set --icon-<name> to any CSS image:

html-demo {
	--icon-reload: url("my-reload.svg");
}

Custom buttons

The built-in buttons live in HTMLDemoElement.buttons, and you can register your own. Buttons are icon-only: the icon comes from --icon-<name>, so define it in CSS (or set icon to the name of another icon to reuse it). Inside action() and available(), this is the <html-demo> element, so you can use e.g. this.code, this.markup (code plus demo-only children), or this.srcdoc (the demo as a standalone document):

HTMLDemoElement.registerButton("run", {
	label: "Run",
	action () {
		new Function(this.code)();
	},
	// Optional: return false to leave the button out of a demo
	available () {
		return this.querySelector("code.language-js") !== null;
	},
});
html-demo {
	--icon-run: url("play.svg");
}

Then use it like any other button: <html-demo buttons="run">. Toolbars update when buttons changes, and demos that mention a button before it is registered catch up once it is.

For completely custom buttons, slot any element with slot="buttons" to place it in the button group, or slot="toolbar" to place it between the adjusters and the buttons. Slotted elements are only placed, not styled:

<html-demo buttons="open">
	<button slot="buttons">Custom</button>
	<input type="range">
</html-demo>

Style isolation

By default the demo is rendered in the light DOM, and thus inherits the normal page styles. In most cases, this is what you want. If not, you can use the isolate attribute to render the demo in a shadow tree, with the UA’s default styles. This works with both modes:

Content-first Code-first
<html-demo isolate>
	<button>Click me</button>
</html-demo>
<html-demo isolate>
	<pre class="language-html"><code>
		&lt;button>Click me&lt;/button>
	</code></pre>
</html-demo>

Isolating in an iframe

Use isolate="iframe" to render the demo as its own document, in an <iframe>. This gives you full isolation: styles, ids, document and window all behave as they would in a standalone page, and (in code-first mode) so do scripts, without any of the shadow tree caveats. Relative URLs resolve against the current page, so you can reference local assets and scripts as usual:

<html-demo isolate="iframe">
	<pre class="language-html"><code>
		&lt;img src="../../logo.svg" alt="Nude UI logo" width="100">
		&lt;script>
			document.currentScript.replaceWith("Hi from iframe script!");
		&lt;/script>
	</code></pre>
</html-demo>

The iframe is exposed as the iframe part. In browsers that support responsive iframes it grows to fit its content (but not below the default iframe height of 150px). Elsewhere, set its height yourself. Do that conditionally, since any explicit height turns content sizing off:

@supports not (frame-sizing: content-block-size) {
	html-demo::part(iframe) {
		height: 20em;
	}
}

Adjusters do not currently affect iframe demos.

Demo-only content

Children with slot="demo" are rendered in the demo but left out of the code. This is useful for helper styles or setup that would clutter the snippet. Works in both modes, and in isolated mode they go into the shadow tree or iframe along with the demo:

<html-demo isolate>
	<style slot="demo">
		.fancy {
			background: rebeccapurple;
			color: white;
			font-weight: bold;
		}
	</style>
	<button class="fancy">Click me</button>
</html-demo>

Execute script

In code-first mode, any <script> elements will also be executed:

<html-demo>
	<pre class="language-html"><code>
		&lt;button>Click me&lt;/button>
		&lt;script>{
			let button = document.currentScript.previousElementSibling;
			// button.onclick = e =>
			button.textContent = "Hi from script!";
		}&lt;/script>
	</code></pre>
</html-demo>

Executing scripts in isolated mode

Do note that there is limited utility in doing this in shadow tree isolation (plain isolate), since there is no (easy) way to get a reference to any of the other elements in the demo (none of this applies to isolate="iframe"):

<html-demo isolate>
	<pre id="isolated-demo" class="language-html"><code>
		&lt;p>This demo has no actual content, but scroll down a bit 👇🏼 &lt;/p>
		&lt;script>{
			let pre = document.getElementById("isolated-demo");
			let container = pre.closest("body > *");
			container.after("Hi from shadow tree script!");
		}&lt;/script>
	</code></pre>
</html-demo>

Auto-wrapping HTML code snippets on a whole page

The element class provides two helper methods for this very thing:

import HTMLDemoElement from "https://nudeui.com/components/html-demo/html-demo.js";

HTMLDemoElement.wrapAll({
	container: mySection,
	ignore: ".no-html-demo, #installation, #some-other-section",
});

All parameters are optional.

Name Default value Description
container document.body The element to search for <html-demo> elements.
ignore "" A CSS selector for elements to ignore.
languages ["html", "markup"] The language-xxx classes whose code snippets to wrap

Installation

Just include the component’s JS file and you’re good:

<script src="https://nudeui.com/components/html-demo/html-demo.js" type="module"></script>

In case you want to link to local files: CSS is fetched automatically, and assumed to be in the same directory as the JS file.