npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

paraframe

v0.2.0

Published

Click through Svelte prototypes like a Figma file: real Svelte components as frames, on a zoomable canvas and in a flow player.

Readme

paraframe — Click through your Svelte prototypes.

paraframe is a read-only viewer for UI frames, in the style of a design tool's prototype preview. You write each frame as a Svelte component, and paraframe lays them out on a canvas and plays them one at a time. Frames are real components, so anything inside them stays interactive.

Try the showcase at https://paraframe.convex.works.

  • Canvas. Every frame in sections on a board you can pan and zoom.
  • Player. One frame at a time, scaled to the window; named flows give you starting points.
  • Real components in iframes. Each frame renders at its own size, with its own styles, media queries and fixed elements, isolated from paraframe's chrome.
  • Frame links. Links, hotspots on any element, and navigation from code.
  • Static previews. Optional images at overview scale that switch to live frames as you zoom in.
  • Comments. Optional pinned threads on elements inside frames, through visual-comments.
  • Theming. --pf-* custom properties for the chrome.

Requires SvelteKit 3 and Svelte 5. The chrome ships as plain CSS, so your app needs no Tailwind.

Quickstart

In a SvelteKit app (a new one from npx sv create --template minimal --types ts works), install paraframe and delete the template's root src/routes/+page.svelte: the canvas takes its place at /.

npm i -D paraframe

Define the prototype once:

// src/lib/prototype.ts
import { resolve } from '$app/paths';
import { definePrototype } from 'paraframe';

export const prototype = definePrototype({
	title: 'My product',
	size: { width: 1440, height: 900 }, // default frame size
	sections: [
		{
			id: 'onboarding',
			title: 'Onboarding',
			frames: [{ id: 'welcome', title: 'Welcome', load: () => import('./frames/Welcome.svelte') }]
		}
	],
	flows: [{ id: 'signup', title: 'Sign up', start: 'welcome' }],
	paths: {
		canvas: () => resolve(''),
		play: (frame) => resolve(`play/${frame}`),
		frame: (frame) => resolve(`frame/${frame}`)
	}
});

Each frame is an ordinary Svelte component:

<!-- src/lib/frames/Welcome.svelte -->
<main>
	<h1>Welcome</h1>
	<p>Your first frame.</p>
</main>

Then add the routes. The canvas and player share a layout that loads paraframe's styles; the frame route loads your product's styles instead.

src/routes/
  +layout.ts
  (viewer)/+layout.svelte
  (viewer)/+page.svelte
  (viewer)/play/[frame]/+page.ts
  (viewer)/play/[frame]/+page.svelte
  frame/[frame]/+page.ts
  frame/[frame]/+page.svelte
// src/routes/+layout.ts
export const prerender = true;
<!-- src/routes/(viewer)/+layout.svelte -->
<script lang="ts">
	import 'paraframe/styles.css';
	import type { LayoutProps } from './$types';

	let { children }: LayoutProps = $props();
</script>

{@render children()}
<!-- src/routes/(viewer)/+page.svelte -->
<script lang="ts">
	import { Canvas } from 'paraframe';
	import { prototype } from '#lib/prototype.js';
</script>

<Canvas {prototype} />
// src/routes/(viewer)/play/[frame]/+page.ts
import { frameEntries } from 'paraframe';
import { prototype } from '#lib/prototype.js';

export const entries = () => frameEntries(prototype);
<!-- src/routes/(viewer)/play/[frame]/+page.svelte -->
<script lang="ts">
	import { page } from '$app/state';
	import { Player } from 'paraframe';
	import { prototype } from '#lib/prototype.js';
</script>

<Player {prototype} frameId={page.params.frame ?? ''} />
// src/routes/frame/[frame]/+page.ts
import { frameEntries, resolveFrame } from 'paraframe';
import { prototype } from '#lib/prototype.js';
import type { PageLoad } from './$types';

export const entries = () => frameEntries(prototype);

export const load: PageLoad = async ({ params }) => ({
	resolved: await resolveFrame(prototype, params.frame)
});
<!-- src/routes/frame/[frame]/+page.svelte -->
<script lang="ts">
	// Import your product's own CSS here, never paraframe/styles.css.
	import { FrameHost } from 'paraframe';
	import { prototype } from '#lib/prototype.js';
	import type { PageProps } from './$types';

	let { data }: PageProps = $props();
</script>

<FrameHost {prototype} resolved={data.resolved} />

Setup covers frame sizes and props, static previews and theming.

To see a complete working prototype, clone this repository and run pnpm install && pnpm showcase.

Linking frames

Inside a frame:

<script lang="ts">
	import { FrameLink, useFrameNav } from 'paraframe';
	const nav = useFrameNav();
</script>

<FrameLink to="details">Open details</FrameLink>

<!-- Any element, e.g. a table row -->
<tr {@attach nav.hotspot('details')}>…</tr>

<!-- From code -->
<button onclick={() => nav.go('details')}>Save</button>

Clicking where there is no link briefly highlights everything that is one. Buttons, inputs and links never trigger this. If another element handles clicks inside the frame (a selectable table row, say), add data-pf-interactive to it.

Keys

| View | Keys | | --- | --- | | Player | ← → previous and next frame, R restart the flow (while one is playing), C comment mode, Esc close the open comment, then leave comment mode | | Canvas | Scroll or drag to pan, ⌘/Ctrl + scroll to zoom, ⇧1 fit all, ⇧0 100%, F or ⇧2 center and fit the selection, ← → select previous and next frame, Enter play (when no button or link has focus), C comment mode, Esc close the open comment, then leave comment mode |

On the canvas, hover live frames to inspect tooltips and hover states, and double-click a frame to play it.

Screenshots

The canvas: every frame of a prototype, in Desktop and Phone sections, with comment pins

The screenshots show an example invoicing prototype; pnpm showcase runs a smaller one.

Comments

Optional. Comments come from a visual-comments server through your prototype's own server, which also signs viewers in. Install visual-comments next to paraframe, wrap the viewer layout in <Comments> from paraframe/comments, and viewers can press C to pin threads on elements inside frames. Without it, paraframe needs nothing from visual-comments. See Comments.

License

MIT