frameset
v0.1.1
Published
A lightweight viewer for iterating on HTML frames alongside a real codebase. A frame is an HTML fragment that imports real components and styles. Frameset discovers those files, wraps them into full documents at serve time, and shows them in a live viewer
Readme
Frameset
A lightweight viewer for iterating on HTML frames alongside a real codebase. A frame is an HTML fragment that imports real components and styles. Frameset discovers those files, wraps them into full documents at serve time, and shows them in a live viewer with hot reload.
Storybook targets isolated component stories. Figma targets visual design. Frameset targets the space between: composing real components into real screens quickly, especially in agent-driven workflows where editing plain HTML is the simplest thing that can work.
Quick start
frameset init
framesetOr point at an existing project:
frameset --path my-projectThis starts a dev server on http://127.0.0.1:4400/, opens the viewer in a browser, and watches for changes.
To work on the viewer UI itself, run:
npm run devThis runs Frameset against the local example/ project, which is the default sandbox for iterating on the viewer UI and example frames together.
Project setup
A Frameset project needs a directory of *.html frame files (default: frames/).
frameset.config.js (or .ts) is optional and only needed when you want to customize settings like dir or globals.
my-project/
frameset.config.js
frames/
dashboard.html
login.html
components/
nav-bar.js
metric-card.jsRun frameset init to scaffold a starter config and frames directory. Or create just the frames directory by hand:
Config
// frameset.config.js
export default {
dir: "frames",
globals: {
theme: {
default: "light",
values: ["light", "dark"],
},
},
applyGlobals({ globals, document }) {
document.documentElement.dataset.theme = globals.theme;
},
};dir— directory to scan for frame HTML files, relative to the project root. Defaults to"frames".globals— defines dropdowns that appear in the viewer toolbar. Each key becomes a dropdown.defaultis optional; if omitted, the first value is used.applyGlobals({ globals, frame, document, window })— called inside each frame when a global value changes. Receives a single context object.documentandwindowrefer to the frame, not the viewer.
Config changes take effect without restarting the server.
Frames
A frame is a body-only HTML fragment — no <!DOCTYPE>, <html>, <head>, or <body> tags. Frameset wraps it into a full document automatically.
<!-- frames/login.html -->
<title>Login card</title>
<script type="module" src="../components/login-panel.js"></script>
<style>
body { background: #f8fafc; }
:root[data-theme="dark"] body { background: #020617; }
</style>
<div class="page">
<login-panel title="Welcome back" primary-label="Continue"></login-panel>
</div>- An optional
<title>tag sets the display name in the sidebar. If omitted, the filename is used. <script>and<style>tags work normally. Relative paths resolve from the frame file's location.- Frames are served through Vite, so any transforms from your
vite.config.jsapply automatically.
Frames are discovered from the top level of the frame directory. Nested subdirectories are ignored for now.
Commands
frameset — start dev server
frameset [options]| Option | Description | Default |
|---|---|---|
| --path <path> | Project root directory | Current working directory |
| --dir <name> | Override the frame directory | "frames" |
| --port <number> | Dev server port | 4400 |
frameset init — scaffold a new project
frameset init [options]| Option | Description | Default |
|---|---|---|
| --dir <name> | Frame directory name | "frames" |
Creates frameset.config.js and the frames directory.
Viewer
The viewer shows a sidebar listing all discovered frames and an iframe preview of the selected frame. If globals are configured, a toolbar with dropdown controls appears below the preview.
The selected frame is stored in the URL hash, so you can bookmark or share links like http://127.0.0.1:4400/#dashboard.
Direct frame routes
Each frame is also available at its own URL:
http://127.0.0.1:4400/frame/dashboard
http://127.0.0.1:4400/frame/loginThese are full HTML pages, useful for testing with Playwright, visual diffing, or accessibility checks — no special story format needed.
Vite integration
Frameset creates a Vite dev server rooted at your project. If you have a vite.config.js, Frameset inherits your plugins, aliases, PostCSS config, and framework transforms. Frameset only overrides what it needs to control (server port, host, and its own routes).
Viewer-only dependencies belong to Frameset itself. If the viewer imports a linked local package, Frameset needs an explicit Vite alias for it in src/server.ts so the viewer still resolves correctly when the dev server root points at another project.
Testing
Since frames are plain URLs, standard browser testing tools work directly:
// Playwright example
await page.goto("http://127.0.0.1:4400/frame/dashboard");
await expect(page.locator("metric-card")).toBeVisible();