@websideproject/nuxt-protokit
@websideproject/nuxt-protokit
Schema-driven prototyping for Nuxt 4. Describe a tool as one TypeScript object — fields, computed values, collections,
results and charts — and <ProtoTool> renders it, saved to IndexedDB through Y.js as you type.
- One schema — 12 field types, derived values, collections with CRUD, result cards, 12 chart and visualization types
- Offline-first — every edit is a local Y.js update persisted to IndexedDB; tabs stay in sync over BroadcastChannel
- Headless — use the persistence layer (
usePrototype,useProtoMap,useProtoList,useProtoText) without any UI - Calendar, item panels, dashboards — ready-made components that read the same schema
- Migrations, namespaces, encryption — versioned schemas, per-tenant isolation, AES-GCM encrypted storage
- Corruption recovery and optional server backup to your own endpoints
- Extensible — custom field and visualization types via
defineProtokitExtension - Nuxt 4 + Nuxt UI (v3 or v4)
Install
npm install @websideproject/nuxt-protokit yjs y-indexeddb @nuxt/ui tailwindcss// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/ui', '@websideproject/nuxt-protokit'],
css: ['~/assets/css/main.css'],
protokit: {
serverSync: false, // local only; see "Server backup" below
},
})The components are styled with Tailwind classes, so Tailwind has to scan the module. Add one @source line to your
stylesheet (the path is relative to the CSS file):
/* app/assets/css/main.css */
@import "tailwindcss";
@import "@nuxt/ui";
@source "../../../node_modules/@websideproject/nuxt-protokit";Quick start
<!-- app/pages/estimator.vue -->
<script setup lang="ts">
import { formatMoney } from '#protokit/utils/formatters'
const estimator = definePrototype({
key: 'resource-estimator', // also the IndexedDB key
title: 'Resource Cost Estimator',
shortTitle: 'Estimator',
description: 'Estimate a project from team size, salaries and duration.',
icon: 'i-lucide-calculator',
fields: {
teamSize: { type: 'number', label: 'Team size', default: 3, trailing: 'people' },
avgMonthlySalary: { type: 'number', label: 'Avg. monthly salary', default: 4500, leading: '$' },
months: { type: 'range', label: 'Duration (months)', min: 1, max: 24, step: 1, default: 6 },
},
derived: {
monthlyCost: { compute: ({ fields }) => fields.teamSize * fields.avgMonthlySalary },
projectTotal: { compute: ({ fields, derived }) => derived.monthlyCost * fields.months },
},
results: [{
title: 'Cost summary',
badge: ({ derived }) => derived.projectTotal < 50_000
? { label: 'Small project', color: 'primary' }
: { label: 'Large project', color: 'warning' },
stats: ({ derived }) => [
{ label: 'Monthly', value: formatMoney(derived.monthlyCost) },
{ label: 'Project total', value: formatMoney(derived.projectTotal) },
],
}],
visualizations: [{
type: 'bar-chart',
title: 'Monthly vs total',
config: {
type: 'bar-chart',
data: ({ derived }) => [
{ label: 'Monthly', value: derived.monthlyCost },
{ label: 'Total', value: derived.projectTotal },
],
},
}],
})
</script>
<template>
<ClientOnly>
<ProtoTool :schema="estimator" />
</ClientOnly>
</template>A form, a result card with a badge and two stats, and a bar chart. Change a value and reload: it is still there,
read back from IndexedDB. definePrototype, the composables and every Proto* component are auto-imported;
helpers with generic names, like formatMoney, are imported from #protokit/utils/… so they never collide with
your app's own.
Without the UI:
const { state, derived, collections, isReady } = usePrototype(estimator)
state.teamSize.value = 5 // persistedIn the playground
The repo's playground runs these; the pictures are its CI screenshots.
A tool from one schema — fields, derived values, a result card and a chart:

ProtoCalendar — month, week and day views with drag and drop:

Item panels — a detail view for the selected collection item:

Visualizations — stat cards, line, area, bar, pie, funnel, benchmark and timeline:

Server backup
With serverSync enabled (the default is true) the client pushes each document's full Y.js state to your
backend and asks it for snapshots, which corruption recovery restores from:
POST /api/yjs/sync, POST /api/yjs/snapshots/create, GET /api/yjs/snapshots/:key,
POST /api/yjs/snapshots/restore, GET /api/yjs/pull (base URL configurable with
serverSync: { baseUrl }). Requests that fail are ignored, so the local copy keeps working without a backend;
set serverSync: false to make no requests at all. Protokit ships the client side only.
Documentation
Full documentation: websideproject.com/docs/nuxt-protokit
- Getting started: Introduction · Concepts · Quick start
- Schemas: Overview · Fields · Collections · Derived values · Connections · Visualizations
- Composables: Overview · usePrototype · useProtoDoc · useProtoCollection
- Components: Overview · ProtoTool · ProtoCrudModal · ProtoCalendar · Item panels
- Offline-first: Overview · Corruption recovery · Document size and GC · Multi-tenant isolation · Encryption
- Advanced: Building a toolkit · Schema patterns · Extensibility · Permissions · Business-logic guards · Headless mode
- Claude Code skills