Skip to content

Web Native

“Web native” is a phrase that gets used loosely. In Kitsune it has a specific meaning:

Build with the browser’s primitives, not in spite of them.

Three commitments fall out of that.

When the platform provides a primitive that does what you want, use it. When it doesn’t, wrap a native primitive in a custom element instead of replacing it with a <div>.

You wantThe platform gives you
A button<button>
A modal<dialog>
A tooltip / dropdown / menupopover
An accordion<details> / <summary>
A form input<input>, <textarea>, <select>
A form-associated custom inputElementInternals
A progress indicator<progress>
A meter<meter>

Kitsune wraps these — never replaces them. <kit-button> puts a real <button> inside. <kit-dialog> puts a real <dialog> inside. The Quill tutorial uses native <details> for collapsible sections rather than building a custom one.

This isn’t a stylistic preference. The platform’s elements are the reason your app is accessible by default. Replace one and you take responsibility for everything it was doing.

Every framework has its own concept of context — React Context, Vue provide/inject, Angular DI. Kitsune doesn’t add a parallel one. The DOM tree is the context graph.

<kit-shell name="quill">
<kit-boundary surface="workspace" feature="notes">
<kit-boundary surface="note-editor" entity-type="note" entity-id="42">
<kit-button meta-event="note.saved">Save</kit-button>
</kit-boundary>
</kit-boundary>
</kit-shell>

When the button is clicked, the runtime walks up from the button through the DOM, collects the surrounding boundaries, and emits an event whose context is { surfaces: ['workspace', 'note-editor'], surface: 'note-editor', feature: 'notes', entity: { type: 'note', id: '42' } }.

You didn’t write a context provider. You wrote nested elements. The DOM tree carried the meaning.

This is the same pattern the platform uses for everything else. Forms are scoped by the nearest <form>. Labels are scoped by the nearest <label>. CSS scopes are scoped by the nearest layer or container. Kitsune extends this principle to product context.

The browser already coordinates components through events. A submit event bubbles up; a submit listener on the form catches it. A click event bubbles up; whoever attached a delegated listener catches it.

Kitsune extends the same pattern to application-level meaning. An interaction emits a semantic event (note.saved) that bubbles up to the boundary, gets enriched, and reaches modules that observe it. A button asks for a command (dialog.open) that bubbles up to the boundary, gets dispatched, and is handled by a module.

There is no global event bus. There is no Redux store. There is no useContext. The runtime is plumbing for events you already think in terms of: something happened and please do this.

Because Kitsune builds on platform primitives, the things you don’t have to learn are large:

  • A virtual DOM model
  • A reconciliation lifecycle
  • A hook ordering rule
  • A render-batching priority queue
  • A server-component / client-component boundary system
  • A custom routing convention
  • A custom form library
  • A CSS-in-JS runtime

The things you do learn:

  • The DOM and its events (you already know these)
  • Lit’s templating (one short page of docs)
  • The Kitsune runtime loop (the next page)
  • The metadata protocol (a one-page reference)
  • A few Kitsune custom elements (kit-shell, kit-boundary, kit-button, kit-dialog, kit-toast-region, kit-field, kit-card)

That’s the surface area.

Honesty: by going web-native you give up some opinionated convenience. There’s no kit dev server that scaffolds routes. There’s no opinionated state management. There’s no specific way to do data fetching — pick fetch, query libraries, server-sent events, whatever fits.

Kitsune is a coordination architecture, not a full-stack framework. For most teams, that’s the right scope. For some teams, it’s a gap.

If you’d like a full-stack framework that integrates the runtime, that’s a future package. The runtime is small and stable enough to build on.