Skip to content

Other Frameworks

Kitsune’s metadata protocol is plain DOM attributes. The runtime listens at <kit-boundary>, which is a custom element that works in any framework. Most integrations are zero-line.

<kit-shell name="my-app">
<kit-boundary surface="page">
<button data-meta-event="checkout.started">Pay</button>
</kit-boundary>
</kit-shell>
<script type="module">
import '@atheory-ai/kitsune-app'
import { defineKitModule } from '@atheory-ai/kitsune-core'
await customElements.whenDefined('kit-shell')
const shell = document.querySelector('kit-shell')
shell.modules = [
defineKitModule({
name: 'analytics',
events: { '*': (e) => console.log(e.type, e.context, e.payload) },
}),
]
</script>

Whatever rendered the <button> doesn’t matter. The boundary parses it on click. This is the integration story for every framework: render through your tooling, attach metadata, the runtime takes over.

Custom elements need to be told they’re not Vue components. Add to your Vue config:

vite.config.ts
import vue from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('kit-') } },
}),
],
}

Then:

<template>
<kit-shell name="vue-app">
<kit-boundary :surface="surface" :feature="feature">
<button data-meta-event="thing.happened" @click="onClick">Click</button>
</kit-boundary>
</kit-shell>
</template>
<script setup>
import '@atheory-ai/kitsune-app'
import '@atheory-ai/kitsune-ui'
import { onMounted } from 'vue'
onMounted(async () => {
await customElements.whenDefined('kit-shell')
document.querySelector('kit-shell').modules = [/* modules */]
})
</script>

A Vue-native adapter is on the roadmap. Until then, the above pattern works.

Svelte 5 has full custom-element support. No special configuration needed.

<script>
import '@atheory-ai/kitsune-app'
import '@atheory-ai/kitsune-ui'
import { onMount } from 'svelte'
onMount(async () => {
await customElements.whenDefined('kit-shell')
document.querySelector('kit-shell').modules = [/* ... */]
})
</script>
<kit-shell name="svelte-app">
<kit-boundary surface="page">
<button data-meta-event="thing.happened">Click</button>
</kit-boundary>
</kit-shell>

A Svelte-native adapter (with stores instead of hooks) is on the roadmap.

import '@atheory-ai/kitsune-app'
export function App() {
return (
<kit-shell name="solid-app" ref={async (shell) => {
await customElements.whenDefined('kit-shell')
shell.modules = [/* ... */]
}}>
<kit-boundary surface="page">
<button data-meta-event="thing.happened">Click</button>
</kit-boundary>
</kit-shell>
)
}

Solid’s fine-grained reactivity composes well with custom elements. Most patterns work without an adapter.

Next.js apps can use Kitsune in two ways:

Client components. Mark your component 'use client', render <kit-shell> inside, install modules in useEffect.

Server components for layout, client islands for interaction. The shell is client-side. Render the layout (boundaries, content) on the server, hydrate one or two interactive islands.

Either way, register custom elements only on the client:

'use client'
import { useEffect } from 'react'
export function KitsuneRoot({ children }: { children: React.ReactNode }) {
useEffect(() => {
Promise.all([
import('@atheory-ai/kitsune-app'),
import('@atheory-ai/kitsune-ui'),
]).then(async () => {
await customElements.whenDefined('kit-shell')
const shell = document.querySelector('kit-shell')!
shell.modules = [/* ... */]
})
}, [])
return <kit-shell name="app">{children}</kit-shell>
}

For React-specific helpers, the React adapter provides KitShellProvider and KitBoundary as a cleaner alternative to raw custom elements. Both work in Next.js.

Same idea as Next.js but with Vue. Configure custom-element compilation, register on client only via nuxt.config.ts plugin:

plugins/kitsune.client.ts
export default defineNuxtPlugin(async () => {
await import('@atheory-ai/kitsune-app')
await import('@atheory-ai/kitsune-ui')
})

Then use <kit-shell> and <kit-boundary> in any component.

The pattern is the same:

  1. Register custom elements client-side only.
  2. Wrap the layout in <kit-shell> and boundaries.
  3. Install modules in a client effect.

The runtime is portable. Anything that ships HTML and JS to a browser can host it.

You need an adapter when you want:

  • Hooks-style ergonomics in your framework’s idiom (useKitEmit() instead of runtime.emit()).
  • Type-safe boundaries as framework components.
  • SSR awareness (e.g., emit nothing during server render).

Until then, custom elements + data-meta-* is plenty.