Viewport Lab is a browser library. It has no backend, no framework runtime, and no required CSS import when you use mount() — styles ship inside the bundle and are injected into a shadow root.
npm install github:aneebji/html-editor#v1.1.0The package is distributed through GitHub. The public npm registry does not currently host it. Import only in a browser environment.
Script tag (no bundler):
<script src="https://aneebji.github.io/html-editor/html-device-preview.umd.js"></script>jsDelivr (GitHub):
<script src="https://cdn.jsdelivr.net/gh/aneebji/html-editor@main/dist/html-device-preview.umd.js"></script>Creates the studio inside target (a CSS selector or HTMLElement) and returns a StudioHandle.
import { mount } from "html-device-preview";
const studio = mount("#preview", {
html: "",
filename: "Untitled.html",
height: "820px",
});| Option | Type | Default | Description |
|---|---|---|---|
html |
string |
"" |
Document to edit. Empty shows the welcome state. |
theme |
"light" | "dark" |
"light" |
Workspace color scheme. |
editorMode |
"source" | "visual" |
"source" |
Initial editor tab. |
onChange |
(html: string) => void |
none | User changes only; setHtml() does not call back. |
shareBaseUrl |
string |
hosted studio URL | URL of a studio that restores #doc= links. Does not change the host page URL. |
filename |
string |
"Untitled.html" |
Name used for Download and shown in the topbar. |
categories |
DeviceCategory[] |
all | Limit the mobile catalog (desktop/custom remain available): iphone, ipad, android-phone, android-tablet. |
devices |
string[] |
popular 12 mobile devices | Selected viewport IDs or category names, including desktop and custom. An empty array selects none. |
customViewports |
CustomViewport[] |
[] |
Additional instance-local viewports. |
compareIds |
string[] |
first 3 selected | 2–3 distinct selected viewport IDs. |
mode |
"grid" | "focus" | "compare" |
"grid" |
Preview layout. |
orientation |
"portrait" | "landscape" |
"portrait" |
Device orientation. |
zoom |
"fit" | "50" | "75" | "100" |
"fit" |
Preview scale. |
showEditor |
boolean |
true |
Show the Visual / Source pane. |
showChrome |
boolean |
true |
Show the File / View / Devices topbar. When false, Copy / Share / Download move to the preview bar. |
height |
CSS length | host height | Applied to the host element. |
DeviceCategory is "iphone" | "ipad" | "android-phone" | "android-tablet".
| Method | Returns | Description |
|---|---|---|
setOptions(options) |
void |
Update runtime workspace options without replacing the HTML or editor. |
setHtml(html) |
void |
Replace the document. |
getHtml() |
string |
Current source, including <head> when present. |
setFilename(name) |
void |
Rename the download file (.html is added if missing). |
getFilename() |
string |
Current filename. |
download() |
void |
Trigger a file download. |
copy() |
Promise<boolean> |
Copy source to the clipboard. |
shareUrl() |
Promise<string> |
URL with the document in #doc=. |
destroy() |
void |
Unmount and remove listeners. |
el |
HTMLElement |
Internal studio root inside the host’s shadow root. |
document.querySelector("#save").onclick = () => studio.download();
const html = studio.getHtml();The bundle registers <html-device-preview> automatically.
<html-device-preview
height="820px"
filename="campaign.html"
devices="iphone,android-phone"
mode="grid">
</html-device-preview>
<script src="https://aneebji.github.io/html-editor/html-device-preview.umd.js"></script>| Attribute | Maps to |
|---|---|
html |
html (also updates the studio when the attribute changes) |
filename |
filename |
theme |
light or dark (live) |
editor-mode |
source or visual (initial) |
devices |
comma-separated category names or ids (live) |
mode |
mode (live) |
orientation |
orientation (live) |
zoom |
zoom (live) |
height |
height |
hide-editor |
showEditor: false (live; removal shows the editor) |
hide-chrome |
showChrome: false |
import {
mount,
defineElement,
DEVICES,
DEVICE_COUNT,
DESKTOP_VIEWPORTS,
SAMPLE_HTML,
SAMPLE_TEMPLATES,
VERSION,
downloadHtml,
copyText,
encodeShare,
decodeShare,
readShareHash,
} from "html-device-preview";On a script tag, the same names live on HtmlDevicePreview.
SAMPLE_HTML is a restaurant landing page you can load with File → Sample templates. It is not mounted by default.
Preview frames use sandbox="allow-scripts allow-forms" and srcdoc. Their opaque origin prevents access to the host DOM and host storage. Documents can still make network requests. Do not add allow-same-origin.
Treat uploaded HTML as untrusted. The studio is an editor and preview, not a sanitizer for publishing to your users.
shareUrl() / Share compress the document with deflate-raw and encode it as #doc=…. Compression is used when supported, with an uncompressed fallback for encoding. URL limits vary by browser and sharing channel; use Download for larger files. The hosted studio restores share links; mount() does not automatically read the hash. Typing does not rewrite it.
const preview = mount(container, {
html: initialHtml,
onChange: html => updateFormState(html),
});
// Later, when the framework unmounts the component:
preview.destroy();Calling mount() again on the same container destroys the previous instance. Editor listeners, preview observers, and pending preview updates are cleaned up.
Web components emit a bubbling, composed html-change event with { html } in event.detail. Subscribe on the custom element or its ancestor. Changes made through setHtml() or the html attribute do not emit this event.
Source changes update the document state immediately, while preview refreshes are debounced by 160 ms. Download and change callbacks always use the latest source.
Visual mode uses DOMPurify to create a basic rich-text editing surface. It excludes scripts, styles, forms, embedded active content, and active attributes. Switching tabs does not rewrite the document. Making a Visual edit replaces its body with the simplified content; use Source to preserve complex layouts. Exported source is not sanitized.
setOptions(patch: RuntimeOptions) accepts theme, devices, mode, orientation, zoom, showEditor, and compareIds. Omitted fields keep their current values. It does not call onChange or emit html-change. Invalid enum values, non-boolean showEditor, or invalid explicit comparisons throw before applying the patch. Unknown device IDs are ignored. Updating devices clears the UI search/family filter so the requested selection is visible.
interface CustomViewport {
id: string;
name: string;
width: number;
height: number;
}Custom IDs must be nonempty, unique across the instance and built-in catalog, and contain only letters, numbers, underscores or hyphens. Names must contain 1–80 characters. Width/height are integers between 240 and 3840. Invalid custom definitions cause mount() to throw. Additions made through Catalog use generated instance-local IDs. Custom definitions are mount-time options; setOptions() can select existing custom viewports.
PreviewViewport is the union of the unchanged mobile Device interface and BrowserViewport. Browser viewports use category desktop or custom, os: "browser", notch: "none", and pixelRatio: 1. ViewportCategory includes both new categories; DeviceCategory remains unchanged. The categories option limits the mobile portion of the catalog.
An explicit compareIds must contain 2–3 distinct IDs present in the resulting selected device set (including devices in the same patch). With fewer than two selected viewports, the UI asks the user to select more. Comparison ignores search/family filters and persists across mode changes. Deselecting a compared viewport removes it from the comparison and fills an empty required slot from the remaining selection when possible.
The studio caches instantiated iframe cards within its instance and hides those outside the active view. Zoom, family filters, layout, theme, rotation and resizing reuse the cards; changing HTML reloads their document. Hidden previews may continue running scripts. Destroying the studio removes all cards and the CodeMirror editor, observers and global listeners.
Removing a live custom-element attribute restores its default (theme="light", mode="grid", orientation="portrait", zoom="fit", default 12 devices). Attribute changes affect only the corresponding option. For custom viewport definitions and explicit comparisons, use mount().
SAMPLE_TEMPLATES: SampleTemplate[] contains ten self-contained HTML pages. Each entry has id, name, category, description, and html strings. SampleTemplate is also exported as a TypeScript type. The gallery uses sandboxed thumbnail frames and releases them when closed. Template selection emits a user document change, preserving the current selected viewports and comparison settings. SAMPLE_HTML remains available and refers to Harbor & Rye.
Device cards expose aria-busy="true" and an individual loading overlay while awaiting their iframe's load event. Setting the same source or changing layout/zoom/theme does not restart the loader. A browser resource failure may still produce a completed load; this is a load indicator, not a validator of page correctness.