The full-stack web framework that speaks jQuery.
Batteries-included TypeScript framework for the modern web — signals, SSR, Web Components,
routing, and more — with a jQuery-inspired API and zero mandatory build step.
New in 1.11.0: Runtime-agnostic SSR now adds DOM-free fallback rendering,
renderToStringAsync(),renderToStream(),renderToResponse(), runtime adapters, hydration strategies, store snapshots, and resumability hooks, alongside the new@bquery/bquery/serverentry point for dependency-free backend routing and WebSocket sessions.
- Full-stack by default: signals, SSR, routing, server middleware, Web Components, and 15+ modules ship together — use only what you need, or bring everything.
- Zero mandatory build step: start in plain HTML with the CDN entry points, or use your preferred bundler without changing the API surface.
- Reactive data across the stack: fetch composables, HTTP clients, polling, pagination, WebSocket / SSE, REST helpers, and request coordination plug directly into signals.
- From UI to backend: declarative views, forms, accessibility helpers, plugins, devtools, testing utilities, SSR, and server routing cover the full app lifecycle.
- TypeScript-first and tree-shakeable: explicit entry points keep large apps typed while letting smaller apps import focused modules.
- Security-focused: DOM writes are sanitized by default, with Trusted Types and CSP helpers built in.
# npm
npm install @bquery/bquery
# bun
bun add @bquery/bquery
# pnpm
pnpm add @bquery/bquery<script type="module">
import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
const count = signal(0);
$('#counter').text(`Count: ${count.value}`);
</script><script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
<script>
const { $, signal } = bQuery;
const count = signal(0);
</script><script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
<script>
const { $, $$ } = bQuery;
$$('.items').addClass('loaded');
</script>// Full bundle (all modules)
import {
$,
signal,
component,
registerDefaultComponents,
defineBqueryConfig,
} from '@bquery/bquery';
// Core only
import { $, $$ } from '@bquery/bquery/core';
// Core utilities (named exports, tree-shakeable)
import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
// Reactive only
import {
signal,
computed,
effect,
linkedSignal,
persistedSignal,
useAsyncData,
useFetch,
createUseFetch,
createHttp,
http,
usePolling,
usePaginatedFetch,
useInfiniteFetch,
useWebSocket,
useWebSocketChannel,
useEventSource,
useResource,
useResourceList,
useSubmit,
createRestClient,
createRequestQueue,
deduplicateRequest,
} from '@bquery/bquery/reactive';
// Concurrency only
import {
batchTasks,
callWorkerMethod,
createReactiveRpcPool,
createReactiveRpcWorker,
createReactiveTaskPool,
createReactiveTaskWorker,
createRpcPool,
createRpcWorker,
createTaskPool,
createTaskWorker,
every,
filter,
find,
getConcurrencySupport,
map,
parallel,
pipeline,
reduce,
runTask,
some,
} from '@bquery/bquery/concurrency';
// Components only
import {
bool,
component,
defineComponent,
html,
registerDefaultComponents,
} from '@bquery/bquery/component';
// Motion only
import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
// Security only
import { sanitize, sanitizeHtml, trusted } from '@bquery/bquery/security';
// Platform only
import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';
// Router, Store, View
import { createRouter, navigate } from '@bquery/bquery/router';
import { createStore, defineStore } from '@bquery/bquery/store';
import { mount, createTemplate } from '@bquery/bquery/view';
// Forms, i18n, accessibility, drag & drop, media
import { createForm, required, email } from '@bquery/bquery/forms';
import { createI18n } from '@bquery/bquery/i18n';
import { trapFocus, rovingTabIndex } from '@bquery/bquery/a11y';
import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
import {
mediaQuery,
useViewport,
useIntersectionObserver,
useResizeObserver,
useMutationObserver,
clipboard,
} from '@bquery/bquery/media';
// Plugins, devtools, testing, SSR, server
import { use } from '@bquery/bquery/plugin';
import { enableDevtools, inspectSignals } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent, waitFor } from '@bquery/bquery/testing';
import { renderToString, hydrateMount, serializeStoreState } from '@bquery/bquery/ssr';
import { createServer } from '@bquery/bquery/server';
// Storybook helpers
import { storyHtml, when } from '@bquery/bquery/storybook';| Module | Status | Description |
|---|---|---|
| Core | Stable | Selectors, DOM manipulation, events, traversal, and typed utilities |
| Reactive | Stable | signal, computed, effect, watchDebounce, watchThrottle, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers |
| Concurrency | Experimental | Zero-build worker tasks, explicit RPC helpers, optional reactive state wrappers, bounded worker pools, high-level collection helpers, and an optional fluent pipeline layer |
| Component | Stable | Typed Web Components with scoped reactivity and configurable Shadow DOM |
| Storybook | Beta | Safe story template helpers with boolean-attribute shorthand |
| Motion | Stable | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines |
| Security | Stable | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition |
| Platform | Stable | Storage, cache, cookies, page metadata, announcers, and shared runtime config |
| Router | Stable | SPA routing, constrained params, redirects, guards, useRoute(), and <bq-link> |
| Store | Stable | Signal-based state management, persistence, migrations, and action hooks |
| View | Beta | Declarative DOM bindings with bq-* directives for content, classes, forms, errors, ARIA, and plugins |
| Forms | Beta | Reactive form state with sync/async validation and submit handling |
| i18n | Beta | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting |
| A11y | Beta | Focus traps, live-region announcements, roving tabindex, skip links, and audits |
| DnD | Beta | Draggable elements, droppable zones, and sortable lists |
| Media | Beta | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers |
| Plugin | Beta | Global plugin registration for custom directives and Web Components |
| Devtools | Beta | Runtime inspection helpers for signals, stores, components, and timelines |
| Testing | Beta | Component mounting, mock signals/router helpers, and async test utilities |
| SSR | Experimental | Runtime-agnostic server-side rendering (Node ≥ 24, Deno, Bun), streaming, async loaders, hydration islands, head/asset/CSP-nonce management, runtime adapters |
| Server | Experimental | Express-inspired backend routing, middleware, safe response helpers, SSR-aware request handling, and runtime-agnostic WebSocket sessions |
Storybook authoring helpers are also available as a dedicated entry point via @bquery/bquery/storybook. Worker-task, RPC, worker-pool, high-level task-list / collection helpers, and the optional fluent pipeline layer ship as a dedicated entry point via @bquery/bquery/concurrency. Server-side middleware, HTTP routing, and runtime-agnostic WebSocket session helpers ship as a dedicated entry point via @bquery/bquery/server.
Reusable workers and pools can also opt into readonly signal mirrors such as state$, busy$, pending$, and size$ through the createReactive*() concurrency wrappers.
bQuery.js covers the full development lifecycle — from interactive DOM scripting to server-side rendering. The examples below show each layer independently; in practice they compose seamlessly.
import { $, $$ } from '@bquery/bquery/core';
$('#save').on('click', (event) => {
console.log('Saved', event.type);
});
$('#list').delegate('click', '.item', (event, target) => {
console.log('Item clicked', target.textContent);
});
$('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
const color = $('#box').css('color');
if ($('#el').is('.active')) {
console.log('Element is active');
}
$$('.container').find('.item').addClass('found');import {
signal,
computed,
effect,
batch,
watch,
watchDebounce,
watchThrottle,
readonly,
linkedSignal,
} from '@bquery/bquery/reactive';
const count = signal(0);
const doubled = computed(() => count.value * 2);
effect(() => {
console.log('Count changed', count.value);
});
watch(count, (newVal, oldVal) => {
console.log(`Changed from ${oldVal} to ${newVal}`);
});
watchDebounce(
count,
(newVal) => {
console.log('Debounced count', newVal);
},
150
);
const readOnlyCount = readonly(count);
batch(() => {
count.value++;
count.value++;
});
count.dispose();
const first = signal('Ada');
const last = signal('Lovelace');
const fullName = linkedSignal(
() => `${first.value} ${last.value}`,
(next) => {
const [nextFirst, nextLast] = next.split(' ');
first.value = nextFirst ?? '';
last.value = nextLast ?? '';
}
);
fullName.value = 'Grace Hopper';import { runTask } from '@bquery/bquery/concurrency';
const total = await runTask(
({ values }: { values: number[] }) => values.reduce((sum, value) => sum + value, 0),
{ values: [1, 2, 3, 4] },
{ timeout: 1_000 }
);
console.log(total); // 10import { createRpcWorker } from '@bquery/bquery/concurrency';
const rpc = createRpcWorker({
formatUser: ({ first, last }: { first: string; last: string }) => `${last}, ${first}`,
sum: ({ values }: { values: number[] }) => values.reduce((total, value) => total + value, 0),
});
console.log(await rpc.call('formatUser', { first: 'Ada', last: 'Lovelace' }));
console.log(await rpc.call('sum', { values: [1, 2, 3] }));
rpc.terminate();import { createTaskPool } from '@bquery/bquery/concurrency';
const pool = createTaskPool(({ value }: { value: number }) => value * 2, {
concurrency: 4,
maxQueue: 16,
name: 'double-pool',
});
const results = await Promise.all([
pool.run({ value: 1 }),
pool.run({ value: 2 }),
pool.run({ value: 3 }),
]);
console.log(results); // [2, 4, 6]
pool.terminate();import { createReactiveTaskPool } from '@bquery/bquery/concurrency';
import { effect } from '@bquery/bquery/reactive';
const pool = createReactiveTaskPool(
async ({ delay, value }: { delay: number; value: number }) => {
await new Promise((resolve) => setTimeout(resolve, delay));
return value * 2;
},
{ concurrency: 2, maxQueue: 8 }
);
effect(() => {
console.log(pool.state$.value, pool.pending$.value, pool.size$.value);
});
await Promise.all([
pool.run({ delay: 20, value: 1 }),
pool.run({ delay: 20, value: 2 }),
pool.run({ delay: 0, value: 3 }),
]);
pool.terminate();import {
batchTasks,
every,
filter,
find,
map,
parallel,
pipeline,
reduce,
some,
} from '@bquery/bquery/concurrency';
const tasks = await parallel([
{ handler: (value: number) => value * 2, input: 5 },
{
handler: ({ first, last }: { first: string; last: string }) => `${last}, ${first}`,
input: { first: 'Ada', last: 'Lovelace' },
},
]);
const batched = await batchTasks(
[
{ handler: (value: number) => value * 2, input: 1 },
{ handler: (value: number) => value * 2, input: 2 },
{ handler: (value: number) => value * 2, input: 3 },
],
2
);
const mapped = await map([1, 2, 3, 4], (value, index) => value + index, {
batchSize: 2,
concurrency: 2,
});
const filtered = await filter([5, 2, 9, 4], (value) => value % 2 === 1);
const hasEven = await some([1, 3, 4], (value) => value % 2 === 0);
const allEven = await every([2, 4, 6], (value) => value % 2 === 0);
const firstLarge = await find([3, 8, 11, 14], (value) => value > 10);
const reduced = await reduce([1, 2, 3, 4], (accumulator, value) => accumulator + value, 0);
const piped = await pipeline([1, 2, 3, 4], { batchSize: 2, concurrency: 2 })
.map((value) => value * 2)
.filter((value) => value > 4)
.toArray();
console.log(tasks, batched, mapped, filtered, hasEven, allEven, firstLarge, reduced, piped);import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';
const userId = signal(1);
const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
baseUrl: 'https://api.example.com',
watch: [userId],
query: { include: 'profile' },
});
const useApiFetch = createUseFetch({
baseUrl: 'https://api.example.com',
headers: { 'x-client': 'bquery-readme' },
});
const settings = useApiFetch<{ theme: string }>('/settings');
console.log(user.pending.value, user.data.value, settings.error.value);import { mount } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';
const formError = signal('');
const fieldState = signal({ invalid: false, describedBy: '' });
mount('#profile-form', {
formError,
fieldState,
});
formError.value = 'Email is required';
fieldState.value = { invalid: true, describedBy: 'email-error' };<input
id="email"
bq-aria="{ invalid: fieldState.value.invalid, 'aria-describedby': fieldState.value.describedBy }"
/>
<p id="email-error" bq-error="formError"></p>import {
createHttp,
createRequestQueue,
deduplicateRequest,
useEventSource,
useWebSocket,
} from '@bquery/bquery/reactive';
const api = createHttp({
baseUrl: 'https://api.example.com',
retry: {
count: 2,
onRetry: (error, attempt) => console.warn(`Retry #${attempt}`, error.message),
},
});
const queue = createRequestQueue({ concurrency: 4 });
const ws = useWebSocket<{ type: string; payload: unknown }>('wss://api.example.com/live');
const sse = useEventSource<{ token: string }>('/api/stream');
const users = await deduplicateRequest('/users', () => queue.add(() => api.get('/users')));
console.log(users.data, ws.status.value, sse.eventName.value);import {
bool,
component,
defineComponent,
html,
registerDefaultComponents,
safeHtml,
} from '@bquery/bquery/component';
import { sanitizeHtml, trusted } from '@bquery/bquery/security';
const badge = trusted(sanitizeHtml('<span class="badge">Active</span>'));
component('user-card', {
props: {
username: { type: String, required: true },
age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
},
state: { count: 0 },
beforeMount() {
console.log('About to mount');
},
connected() {
console.log('Mounted');
},
beforeUpdate(newProps, oldProps) {
return newProps.username !== oldProps.username;
},
updated(change) {
console.log('Updated because of', change?.name ?? 'state/signal change');
},
onError(error) {
console.error('Component error:', error);
},
render({ props, state }) {
return safeHtml`
<button class="user-card" ${bool('disabled', state.count > 3)}>
${badge}
<span>Hello ${props.username}</span>
</button>
`;
},
});
const UserCard = defineComponent('user-card-manual', {
props: { username: { type: String, required: true } },
render: ({ props }) => html`<div>Hello ${props.username}</div>`,
});
customElements.define('user-card-manual', UserCard);
const tags = registerDefaultComponents({ prefix: 'ui' });
console.log(tags.button); // ui-buttonimport { storyHtml, when } from '@bquery/bquery/storybook';
export const Primary = {
args: { disabled: false, label: 'Save' },
render: ({ disabled, label }) =>
storyHtml`
<ui-card>
<ui-button ?disabled=${disabled}>${label}</ui-button>
${when(!disabled, '<small>Ready to submit</small>')}
</ui-card>
`,
};import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
await transition({
update: () => {
$('#content').text('Updated');
},
classes: ['page-transition'],
types: ['navigation'],
skipOnReducedMotion: true,
});
await animate(card, {
keyframes: keyframePresets.pop(),
options: { duration: 240, easing: 'ease-out' },
});
const x = spring(0, { stiffness: 120, damping: 14 });
x.onChange((value) => {
element.style.transform = `translateX(${value}px)`;
});
await x.to(100);import { sanitize, escapeHtml, sanitizeHtml, trusted } from '@bquery/bquery/security';
import { safeHtml } from '@bquery/bquery/component';
const safeMarkup = sanitize(userInput);
const safe = sanitize('<form id="cookie">...</form>');
const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
const escaped = escapeHtml('<script>alert(1)</script>');
const icon = trusted(sanitizeHtml('<span class="icon">♥</span>'));
const button = safeHtml`<button>${icon}<span>Save</span></button>`;import {
defineBqueryConfig,
useCookie,
definePageMeta,
useAnnouncer,
storage,
notifications,
} from '@bquery/bquery/platform';
defineBqueryConfig({
fetch: { baseUrl: 'https://api.example.com' },
transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
components: { prefix: 'ui' },
});
const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
const cleanupMeta = definePageMeta({ title: 'Dashboard' });
const announcer = useAnnouncer();
theme.value = 'dark';
announcer.announce('Preferences saved');
cleanupMeta();
const local = storage.local();
await local.set('theme', theme.value);
const permission = await notifications.requestPermission();
if (permission === 'granted') {
notifications.send('Build complete', {
body: 'Your docs are ready.',
});
}import {
mediaQuery,
useViewport,
useIntersectionObserver,
useResizeObserver,
useMutationObserver,
} from '@bquery/bquery/media';
import { effect } from '@bquery/bquery/reactive';
const prefersDark = mediaQuery('(prefers-color-scheme: dark)');
const viewport = useViewport();
const intersection = useIntersectionObserver(document.querySelector('#hero'));
const resize = useResizeObserver(document.querySelector('#panel'));
const mutations = useMutationObserver(document.querySelector('#feed'), {
childList: true,
subtree: true,
});
effect(() => {
console.log(prefersDark.value, viewport.value.width, intersection.value.isIntersecting);
});
console.log(resize.value.width, mutations.value.mutations.length);import { effect } from '@bquery/bquery/reactive';
import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
const router = createRouter({
routes: [
{ path: '/', name: 'home', component: HomePage },
{ path: '/user/:id', name: 'user', component: UserPage },
{ path: '*', component: NotFound },
],
});
router.beforeEach(async (to) => {
if (to.path === '/admin' && !isAuthenticated()) {
await navigate('/login');
return false;
}
});
effect(() => {
console.log('Current path:', currentRoute.value.path);
});import { createForm, email, required } from '@bquery/bquery/forms';
const form = createForm({
fields: {
name: { initialValue: '', validators: [required()] },
email: { initialValue: '', validators: [required(), email()] },
},
onSubmit: async (values) => {
await fetch('/api/signup', {
method: 'POST',
body: JSON.stringify(values),
});
},
});
await form.handleSubmit();
console.log(form.isValid.value, form.fields.email.error.value);import { createI18n } from '@bquery/bquery/i18n';
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
en: { greeting: 'Hello, {name}!' },
de: { greeting: 'Hallo, {name}!' },
},
});
console.log(i18n.t('greeting', { name: 'Ada' }));
i18n.$locale.value = 'de';import { trapFocus, announceToScreenReader } from '@bquery/bquery/a11y';
import { mediaQuery, useViewport } from '@bquery/bquery/media';
import { draggable } from '@bquery/bquery/dnd';
const modalTrap = trapFocus(document.querySelector('#dialog')!);
announceToScreenReader('Dialog opened');
const isDark = mediaQuery('(prefers-color-scheme: dark)');
const viewport = useViewport();
const drag = draggable(document.querySelector('#card')!, { bounds: 'parent' });
console.log(isDark.value, viewport.value.width);
drag.destroy();
modalTrap.release();import { use } from '@bquery/bquery/plugin';
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
import { renderToString } from '@bquery/bquery/ssr';
import { createServer } from '@bquery/bquery/server';
use({
name: 'focus-plugin',
install(ctx) {
ctx.directive('focus', (el) => (el as HTMLElement).focus());
},
});
enableDevtools(true, { logToConsole: true });
console.log(getTimeline());
const mounted = renderComponent('ui-button', { props: { variant: 'primary' } });
fireEvent(mounted.el, 'click');
const { html } = renderToString('<p bq-text="label"></p>', { label: 'Hello SSR' });
console.log(html);
const app = createServer();
app.get('/hello/:name', (ctx) => ctx.json({ name: ctx.params.name, q: ctx.query.q }));
// Runtime-agnostic async render with head injection (works on Node, Deno, Bun):
import { createSSRContext, renderToResponse } from '@bquery/bquery/ssr';
const ctx = createSSRContext({ request: new Request('http://localhost/') });
ctx.head.add({ title: 'Home' });
ctx.assets.module('/app.js');
const response = await renderToResponse(
'<html><head></head><body><p bq-text="label"></p></body></html>',
{ label: 'Hello' },
{ context: ctx, etag: true }
);
mounted.unmount();import {
createStore,
createPersistedStore,
defineStore,
mapGetters,
watchStore,
} from '@bquery/bquery/store';
const counterStore = createStore({
id: 'counter',
state: () => ({ count: 0, name: 'Counter' }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
const settingsStore = createPersistedStore({
id: 'settings',
state: () => ({ theme: 'dark', language: 'en' }),
});
const useCounter = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
const counter = useCounter();
const getters = mapGetters(counter, ['doubled']);
watchStore(
counter,
(state) => state.count,
(value) => {
console.log('Count changed:', value, getters.doubled);
}
);import { mount, createTemplate } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';
const count = signal(0);
const items = signal(['Apple', 'Banana', 'Cherry']);
mount('#app', {
count,
items,
increment: () => count.value++,
});| Browser | Version | Support |
|---|---|---|
| Chrome | 90+ | ✅ Full |
| Firefox | 90+ | ✅ Full |
| Safari | 15+ | ✅ Full |
| Edge | 90+ | ✅ Full |
No IE support by design.
Server-side runtimes: Node.js ≥ 24, Bun ≥ 1.3.13, and Deno 2 are supported for SSR and server modules.
- Getting Started: docs/guide/getting-started.md
- Core API: docs/guide/api-core.md
- Agents: docs/guide/agents.md
- Components: docs/guide/components.md
- Storybook: docs/guide/storybook.md
- Reactivity: docs/guide/reactive.md
- Motion: docs/guide/motion.md
- Security: docs/guide/security.md
- Platform: docs/guide/platform.md
- Router: docs/guide/router.md
- Store: docs/guide/store.md
- View: docs/guide/view.md
- Forms: docs/guide/forms.md
- i18n: docs/guide/i18n.md
- Accessibility: docs/guide/a11y.md
- Drag & Drop: docs/guide/dnd.md
- Media: docs/guide/media.md
- Plugin System: docs/guide/plugin.md
- Devtools: docs/guide/devtools.md
- Testing Utilities: docs/guide/testing.md
- SSR / Hydration: docs/guide/ssr.md
- Server: docs/guide/server.md
The cross-runtime SSR examples in examples/ import directly from src/, so you can run them from a repo checkout without building dist/ first.
# Install dependencies
bun install
# Start VitePress docs
bun run dev
# Run Storybook
bun run storybook
# Run tests
bun test
# Build library
bun run build
# Verify AI guidance / release metadata sync
bun run check:ai-guidance
# Build docs
bun run build:docs
# Generate API documentation
bun run docs:api
# Run the cross-runtime SSR examples directly from source
bun examples/ssr-bun/serve.ts
deno run -A examples/ssr-deno/serve.ts
node --experimental-strip-types examples/ssr-node/serve.tsbQuery.js
├── src/
│ ├── core/ # Selectors, DOM ops, events, utils
│ ├── reactive/ # Signals, computed, effects, async data
│ ├── concurrency/ # Zero-build worker tasks, RPC, pools, collection helpers
│ ├── component/ # Web Components helper + default library
│ ├── storybook/ # Story template helpers
│ ├── motion/ # View transitions, FLIP, springs
│ ├── security/ # Sanitizer, CSP, Trusted Types
│ ├── platform/ # Storage, cache, cookies, meta, config
│ ├── router/ # SPA routing, navigation guards
│ ├── store/ # State management, persistence
│ ├── view/ # Declarative DOM bindings
│ ├── forms/ # Reactive forms + validators
│ ├── i18n/ # Internationalization + formatting
│ ├── a11y/ # Accessibility utilities
│ ├── dnd/ # Drag & drop helpers
│ ├── media/ # Browser and device reactive signals
│ ├── plugin/ # Global plugin system
│ ├── devtools/ # Runtime inspection helpers
│ ├── testing/ # Test utilities
│ ├── ssr/ # Runtime-agnostic server-side rendering + hydration
│ └── server/ # Backend helpers and WebSocket sessions
├── docs/ # VitePress documentation
├── .storybook/ # Storybook config
├── stories/ # Component stories
├── tests/ # bun:test suites
└── dist/ # Built files (ESM, UMD, IIFE)
See CONTRIBUTING.md for guidelines.
This project provides dedicated context files for AI coding agents:
- AGENT.md — Architecture, module API reference, coding conventions, common tasks
- llms.txt — Compact LLM-optimized project summary
- .github/copilot-instructions.md — GitHub Copilot context
bun run check:ai-guidance— Lightweight sync check for version / engine / AI guidance drift
MIT – See LICENSE.md for details.