HTML Demo
<html-demo>
Mature
JS
An element for displaying HTML content alongside its source code. Great for documenting web components!
Features
- Provide a code snippet and it will create the demo, or provide the demo and it will create the code snippet.
- Demo inherits page styles but you can optionally isolate it, in a shadow tree or an iframe
- Executes
<script>tags (in code-first mode) - Toolbar buttons to open the demo in a new tab or CodePen, reload it, or anything else you define
Roadmap
From most to least likely to be implemented:
- More style customization (parts, CSS properties)
- Option to collapse code by default
- Structured attribute values
- Work with CSS and JS snippets (without having to include them in HTML markup)
- Different layouts
- Editable examples
Examples
Basic
Code-first:
<html-demo>
<pre class="language-html"><code>
<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 |
|---|---|
|
|
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>
<img src="../../logo.svg" alt="Nude UI logo" width="100">
<script>
document.currentScript.replaceWith("Hi from iframe script!");
</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>
<button>Click me</button>
<script>{
let button = document.currentScript.previousElementSibling;
// button.onclick = e =>
button.textContent = "Hi from script!";
}</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"):
document.currentScriptisnullin shadow trees- All
document.querySelector*()ordocument.getElementBy*()calls will query the light DOM - Ids will not create variables
thiswill be the globalwindowobject orundefinedin module scripts.
<html-demo isolate>
<pre id="isolated-demo" class="language-html"><code>
<p>This demo has no actual content, but scroll down a bit 👇🏼 </p>
<script>{
let pre = document.getElementById("isolated-demo");
let container = pre.closest("body > *");
container.after("Hi from shadow tree script!");
}</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.