@madojs/mado
v0.10.1
Published
Mado — a calm browser-native SPA framework for internal tools, admin panels and business apps. Routing, forms, state and data fetching without frontend infrastructure overhead.
Maintainers
Readme
Mado
A calm frontend stack for internal tools, admin panels and business SPA.
Mado is a browser-native SPA framework for teams that want routing, forms, data fetching and state management — without turning their frontend into an infrastructure project.
You write TypeScript, run tsc, and open the browser. No JSX transform, no
Vite required, no hidden build pipeline. The entire runtime is readable in an
evening. When something breaks, you can read the source and fix it.
Mado (窓) means "window" in Japanese: a calm window into your app, without
dragging a whole frontend factory into the room.
When to use Mado
- Admin panels and dashboards — forms, tables, filters, auth, role guards.
- Internal tools and backoffice — CRUD workflows, settings, billing UI.
- Small SaaS frontends — where long-term maintainability matters more than ecosystem size.
- Embedded widgets — where small footprint and independence from host frameworks matter.
The common thread: apps where the frontend should not become its own infrastructure problem.
When not to use Mado
- SEO-heavy public sites that need SSR with hydration.
- Large teams optimizing for hiring compatibility — React/Vue have bigger talent pools.
- Projects that need a mature UI-kit ecosystem comparable to React today.
- Beginners learning frontend — React, Vue and Svelte have far larger learning resources.
- Teams uncomfortable with a pre-v1 framework — Mado is honest about its stage.
What Mado will not build
Mado stays useful by saying no. These are intentionally out of scope:
- SSR with hydration. Use
bakeor edge prerender for SEO-oriented static output. - A template compiler, JSX transform, or VDOM compatibility layer.
- A separate store library. Use
signal(),computed()andresource(). - Suspense primitives or a router plugin system.
- Built-in i18n, animation, or virtual-scroll primitives.
- Non-evergreen browser support. The baseline is modern evergreen browsers with Baseline 2023 platform features.
Why teams pick Mado
| What matters to you | Best choice | |---|---| | Largest ecosystem, most hires available | React or Vue | | Reusable design-system components across host frameworks | Lit | | Maximum rendering performance, JSX workflow | Solid or Svelte 5 | | Progressive enhancement of server-rendered pages | htmx + your backend | | Full app stack with minimal infrastructure and calm maintenance | Mado |
Honest tradeoffs:
- vs Lit — Lit is better for design systems. Mado is for whole apps: router, data, forms and prerender in one package, no assembly required.
- vs Solid — Solid is faster and more mature. It also requires Vite + a
babel plugin. Mado requires nothing but
tsc. - vs htmx — htmx is excellent when your backend owns HTML. Mado is for cases where you want a real SPA: local state, optimistic updates, cached resources, lazy modules and persisted UI state.
What you get
Routing, forms, state, data fetching and prerendering — without ecosystem tax:
- No runtime dependencies to audit, update or break
- No bundler required to start (
tscis enough) - Fewer moving parts to debug
- Compact API surface you can learn in a day
- Lower long-term cognitive load
Runtime budget:
enforced in CI with npm run size
Runtime dependencies: 0
Required dev dependencies: typescript, esbuild, linkedomQuick Start
Start a new app
npm exec --package @madojs/mado@latest -- mado init my-app
cd my-app
npm install
npm run devThe admin starter gives you the blessed production shape: layouts, guards, auth/API client, forms and a small admin shell.
npm exec --package @madojs/mado@latest -- mado init dashboard --starter admin
cd dashboard
npm install
npm run devThe CRUD starter is a compact resource/mutation/forms example:
npm exec --package @madojs/mado@latest -- mado init my-app --starter crudTry the flagship example
git clone https://github.com/madojs/mado.git
cd mado
npm install
npm run build
npm run serve -- showcaseThe showcase is a CRM-shaped pressure app with auth, tables, filters, nested routes, context services, forms and real data patterns.
How it works
Signals — reactive state
import { signal, computed, effect } from "@madojs/mado";
const count = signal(0);
const doubled = computed(() => count() * 2);
effect(() => console.log(count()));
count.set(1);Signals are getter functions: read with count(), write with count.set(v)
or count.update(fn).
Templates — tagged template html
html`<button @click=${fn} ?disabled=${loading} class=${cls}>${label}</button>`;${value}— child content (text, nodes, arrays, nestedhtml,each)@event=${fn}— event listenerattr=${v}— attribute.prop=${v}— DOM property?attr=${flag}— boolean attribute- Functions and signals are tracked reactively
Components — Web Components
import { component, css, html } from "@madojs/mado";
component(
"x-card",
() => () => html`<section><slot></slot></section>`,
{
styles: css`:host { display: block; padding: 1rem; }`,
},
);Routing — file-based-free
import { routes } from "@madojs/mado";
export default routes({
"/": () => import("./pages/home.js"),
"/users/:id": () => import("./pages/user-detail.js"),
"*": () => import("./pages/not-found.js"),
});Lazy loading, nested routes, query params, guards, hover prefetch, scroll restoration, error boundary, View Transitions.
Data — resource + mutation
import { resource, mutation, invalidate, jsonFetcher } from "@madojs/mado";
const user = resource(
() => `/api/users/${userId()}`,
jsonFetcher<User>(),
{ staleTime: 60_000 },
);
const save = mutation(api.saveUser, {
invalidates: ["/api/users*"],
});Cache, loading/error state, abort, refresh, optimistic mutate(),
glob-based invalidation. Lifecycle-aware inside components.
Resource keys are the cache identity. Use keys that include the endpoint, params and data shape; two resources with the same key share cached data and in-flight requests.
Forms — schema-based validation
import { useForm, html } from "@madojs/mado";
const form = useForm({
email: { required: true, type: "email" },
age: { type: "number", min: 18 },
});
html`
<form @submit=${form.onSubmit(async (values) => api.save(values))}>
<input name="email" @input=${form.onInput} @blur=${form.onBlur}>
<button ?disabled=${() => !form.isValid() || form.submitting()}>Save</button>
</form>
`;HTML-like constraints (required, min, max, pattern, type), async
validators, field arrays. Close to the platform, not fighting it.
Lists — keyed reconciliation
import { each } from "@madojs/mado";
html`<ul>${() => each(items(), (item) => item.id, (item) => html`<li>${item.name}</li>`)}</ul>`;Static prerender — SEO without SSR
mado releaseBuild-time prerender of routes into static HTML with meta tags and JSON-LD.
No hydration runtime. For dynamic content, see the Cloudflare edge-prerender
PoC in examples/cloudflare.
Production
mado release # typecheck + build + bundle + bake + promote baked HTML + copy public -> out/
mado preview # serve out/ like a static hostOne command, one artifact (out/). Upload anywhere: VPS, Cloudflare Pages,
any static CDN.
CLI
mado init my-app # scaffold new app
mado init dashboard --starter admin
mado dev # dev server with hot reload
mado build # tsc compile
mado typecheck # type check without emit
mado test # run test suite
mado release # full production build
mado preview # serve production build locallyDocumentation
- The Mado way — conventions and principles
- Routing
- Project layout
- Static bake & SEO
- App architecture
- Layouts
- Auth and API
- Deployment
- Testing
- Error handling
- For backend developers
- Why Mado (detailed comparison)
Localized docs: French · Ukrainian · Russian
AI-agent entrypoints: AGENTS.md · llms.txt
Examples
examples/showcase— flagship CRM pressure app (auth, tables, filters, forms, nested routes, context services).examples/tickets— CRUD validation app.examples/basic— minimal API tour.examples/cloudflare— edge prerender PoC.
Known Limits
| Limit | What it means |
|---|---|
| No SSR hydration | Use bake or edge prerender for SEO. Server rendering is out of scope. |
| Small ecosystem | No UI-kit or plugin marketplace. You own your components. |
| Pre-v1 API | Public API is small and intentional, but may change before v1. |
| Evergreen browsers only | Modern Chrome, Edge, Firefox, Safari. No IE/legacy. |
| Template IDE support | `html`` highlighting needs lit-plugin or similar. |
Tests
npm run typecheck
npm run build
npm test
npm run test:browserCovers signals, computed, effects, html parser, keyed reconciliation, resources, mutations, forms, router isolation, component lifecycle and example smoke tests.
Contributing
Read CONTRIBUTING.md. Bug fixes with tests, docs improvements, examples and carefully discussed core changes are welcome. Runtime dependencies are not.
License
MIT.
