Skip to content

Add a <playground-preview> web component package #52

@titouanmathis

Description

@titouanmathis

Summary

Add a new @studiometa/playground-preview package that provides a <playground-preview> custom element to embed playground previews anywhere (docs sites, blogs, READMEs, any HTML page) — framework-agnostic.

This is inspired by the PreviewPlayground.vue component from @studiometa/ui docs, but as a standalone web component with zero framework dependency.

API

Short content via attributes

<playground-preview html="<h1>Hi</h1>" script="console.log(1)"></playground-preview>

Long content via <script> children

<playground-preview height="80vh" theme="dark">
  <script type="playground/html">
    <div class="flex items-center gap-4">
      <h1>Hello World</h1>
    </div>
  </script>
  <script type="playground/script">
    import { Base, createApp } from '@studiometa/js-toolkit';

    class App extends Base {
      static config = { name: 'App' };
      mounted() { console.log('mounted!'); }
    }

    export default createApp(App);
  </script>
  <script type="playground/css">
    @import "tailwindcss";
    h1 { @apply text-4xl font-bold; }
  </script>
</playground-preview>

Children <script type="playground/..."> take precedence over attributes when both are provided.

Attributes

Attribute Type Default Description
html string "" HTML content (short inline)
script string "" JavaScript content (short inline)
css string "" CSS content (short inline)
base-url string https://studiometa-playground.pages.dev Playground instance URL
height string 60vh Container height
zoom number 0.9 Initial iframe scale
theme string auto dark | light | auto (uses prefers-color-scheme)
no-controls boolean false Hide zoom/reload/open controls
header string Passed through to playground URL

Exports

// Auto-register — side-effect import, defines <playground-preview>
import '@studiometa/playground-preview';

// Manual registration — class only
import { PlaygroundPreview } from '@studiometa/playground-preview/element';
customElements.define('my-playground', PlaygroundPreview);

Design decisions

Separate package

The web component is a thin shell (iframe + zip + controls). It should be ~5KB bundled. Keeping it separate from @studiometa/playground avoids forcing consumers to install heavy deps (esbuild-wasm, Monaco, webpack-config, etc.) just for an embed widget.

Shadow DOM

Style encapsulation ensures the component's controls and layout are not affected by the host page's CSS. Theming is exposed via CSS custom properties:

playground-preview {
  --pg-bg: #f5f5f5;
  --pg-controls-bg: rgba(0, 0, 0, 0.6);
  --pg-controls-color: white;
  --pg-border-radius: 8px;
}

Controls

  • Zoom in / out / reset
  • Reload iframe
  • Open in new window (links to full playground with editors enabled)

Lazy loading

Uses IntersectionObserver to defer iframe creation until the component scrolls into view. Shows a loader overlay while the iframe is loading.

Observed attributes & reactivity

  • html, script, css, theme, base-url, header → rebuild iframe URL
  • height, zoom → update styles only
  • no-controls → toggle controls visibility
  • <script type="playground/..."> children are read once at connectedCallback

URL building

Params are placed in the hash (not query string) to match the existing playground URL format and avoid server-side issues with long URLs:

{base-url}/#html=<zipped>&script=<zipped>&style=<zipped>&html-editor=false&script-editor=false&style-editor=false&theme=dark

The "open in new window" link uses the same URL but without *-editor=false so editors are visible.

Build

  • ESM only output
  • esbuild (same approach as the main package)
  • Single dependency: fflate (for zip compression)

Package structure

packages/playground-preview/
  package.json
  tsconfig.json
  tsconfig.build.json
  src/
    index.ts                    # re-exports class + auto-defines the element
    element.ts                  # exports the PlaygroundPreview class only
    playground-preview.ts       # the custom element implementation
    zip.ts                      # zip util (uses fflate)
    styles.ts                   # shadow DOM CSS as template literal
    icons.ts                    # inline SVG icon strings

Tasks

  • Scaffold packages/playground-preview package
  • Implement PlaygroundPreview custom element
  • Shadow DOM styles + CSS custom properties theming
  • IntersectionObserver lazy loading
  • Zoom / reload / open-in-new-window controls
  • <script type="playground/..."> child content support
  • Attribute observation & reactivity
  • Build setup (esbuild, ESM)
  • Add to workspace package.json
  • Documentation in README
  • Test in the demo package

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions