Symbiote.js

Tired of pointless re-renders? Bloated bundles? Chaotic data flow? Laggy UI? Magic abstractions and compiler quirks? Done with frameworks that add complexity instead of removing it?

Miss the vanilla flavor, but still want great DX?

Symbiote.js is here to save you

Documentation Code Playground npm GitHub Community

So, what's the big idea?

Symbiote.js isn't trying to be yet another frontend library. Think of it more like a philosophy for web development that embraces the power of what's already built into your browser.

We believe in using modern, standard web technologies, not reinventing them. Symbiote.js simply adds a touch of modern developer experience (DX) on top of vanilla Custom Elements. You get reactive data bindings, flexible HTML-based templates, powerful state management, computed properties, built-in routing, SSR support, and easily extendible components — all with minimal boilerplate and zero build-step requirement.

Here's what makes it feel a bit different:

It's a Pure DOM Extension

Unlike frameworks that wrap your code in complex abstractions (virtual DOM, JSX compilers, proprietary syntax), Symbiote.js works directly with the native DOM API. For you, this means:

  • No black boxes: What you write is what the browser runs. No compilation step, no transpiler magic.
  • Native speed: It leverages the browser's own optimizations. No virtual DOM diffing overhead.
  • No framework lock-in: It plays nicely with any other web technology you want to use.
  • Feather-light: At only ~6kb (br/gzip), it won't weigh your pages down.
  • Zero dependencies: It's all built on native web platform features.
  • Real DOM elements: Symbiote components are actual DOM elements, so you can interact with them just like any other.
  • No build step required: Works directly in the browser with a single HTML file.
  • The classic trio: Just HTML, CSS, and JavaScript. Everything you need for solid, enterprise-ready work.

Simple HTML String Templates

We take a refreshingly simple approach to templating:

  • Just HTML: Use standard tags and attributes with a simple binding syntax for reactivity.
  • Plays well with others: Works with any templating system or server-side tech.
  • Loosely Coupled: Define your templates anywhere — client-side, server-side, or in separate files.
  • Ready for SSR: A perfect fit for server-side rendering and static site generation.
  • Integrates with anything: Easily use it alongside React, Vue, Angular, or any other framework.
  • Built to scale: Powerful enough for even the most complex applications.
// Define templates anywhere, client or server:
import html from '@symbiotejs/symbiote/core/html.js';

const myTemplate = html`
  <div class="my-component" ${{onclick: 'showBubble'}}>
    <h1>{{title}}</h1>
    <div>{{content}}</div>
  </div>
`;

DOM-Based Data Context

This is where things get interesting. Symbiote.js has a fresh take on how components talk to each other and manage state:

  • Natural data flow: It uses the DOM structure itself as a natural way to create data context.
  • Compose with ease: Components can share data just by their position in the DOM tree.
  • CSS-like cascade: Properties can be inherited from parent components, much like CSS styles.
  • Loosely coupled state: Components can share state without being tightly connected.
  • Powerful named contexts: Create app-wide data sources that any component can tap into by name.
// Inherited context (^)
html`<button ${{onclick: '^onButtonClicked'}}>Click me!</button>`;

// Shared context (*)
html`<div>{{*sharedProperty}}</div>`;

// Named context (/)
html`<div>{{APP/someProperty}}</div>`;

Powerful Itemize API

Render dynamic lists declaratively — no manual DOM manipulation, no key prop juggling. Just point itemize at a data array or object and define an inline <template> for each item:

<div itemize="users">
  <template>
    <span>{{name}}</span><span>{{role}}</span>
    <button ${{onclick: '^removeUser'}}>Remove</button>
  </template>
</div>
  • Keyed updates: Optional keyed processor for efficient reordering and minimal DOM mutations.
  • Auto-animated removals: Items with CSS transitions animate out automatically via the [leaving] attribute — zero JS needed.
  • Custom item components: Use item-tag to delegate rendering to a dedicated custom element for complex list items.
  • Parent method access: The ^ prefix binds event handlers to the parent component context — items act as isolated state scopes.
  • List nesting: Nested lists are supported for complex tree-like structured data.

Server Side Rendering

Many developers still think that SSR with Web Components is impossible. Not with Symbiote.js!

Use same-code components on server and client. Just one flag ssrMode = true for efficient hydration. One SSR class for server-side markup generation. Streaming is also supported. Light DOM styles and Declarative Shadow DOM — all handled automatically during SSR output.

SSR — as simple as never before

Built-in SPA Router

Full-featured client-side routing, right out of the box — no extra dependencies needed:

  • Path-based routes with :param extraction
  • Route guards for authentication and access control
  • Lazy loading of route components
  • Named routing context — bind route data directly in templates

CSS-Driven Animations

Exit transitions with zero JS animation code. The animateOut helper sets a [leaving] attribute, waits for CSS transitionend, then removes the element. Works automatically with the Itemize list rendering API.

Enterprise-Grade Security

  • Trusted Types compatible — template writes use a named 'symbiote' policy when the API is available.
  • Full CSP compliance — works with the strictest Content Security Policy headers.

Symbiote.js vs React / Next.js vs Lit

Let's highlight the key differences:

Symbiote.jsReact / Next.jsLit
Size~6 KB gzip~44 KB (React+DOM), Next.js adds much more~16 KB gzip
Virtual DOMNone — direct DOMVirtual DOM + diffingNone — direct DOM
Compiler / BuildNot requiredRequired (JSX, bundler)Not required
StandardNative Web ComponentsProprietary component modelNative Web Components
TemplatesContext-less HTML stringsJSX (JS only)Tagged template literals (JS only)
SSRBuilt-in, streamingNext.js (complex setup)Limited (@lit-labs/ssr)
RoutingBuilt-in (SPA)Next.js filesystem routingNot included
State ManagementBuilt-in PubSub + contextsExternal (Redux, Zustand, etc.)Not included
Computed PropsAuto-trackeduseMemo (manual deps)Manual
CSS ScopingLight DOM + Shadow DOMCSS Modules / CSS-in-JSShadow DOM only
Framework Lock-inNoneHighLow
CSP / Trusted TypesBuilt-inDepends on setupPartial
Dependencies0Many1 (lit-html)

Why not React? React requires a build pipeline, proprietary JSX syntax, a virtual DOM layer that adds overhead, and external libraries for routing, state, and SSR. A full Next.js stack is hundreds of kilobytes. Symbiote.js gives you all of that in ~6 KB with zero lock-in.

Why not Lit? Lit is a solid Web Components library, but it lacks built-in routing, state management, SSR streaming, and computed properties. It forces Shadow DOM for styling, which can be limiting. Symbiote.js offers more batteries-included features at a smaller footprint, plus unique concepts like DOM-based data context and CSS Data binding.

Where It Really Shines

  • Building rich multipart widgets
  • Crafting complex interactive components
  • Micro-frontend architectures
  • Creating reusable component libraries
  • High-performance web apps
  • Framework-agnostic solutions
  • Meta-applications
  • JamStack / Hybrid sites

The Core Goodness

  • Loosely Coupled Architecture
  • Ultralight: Just ~6kb (br/gzip)
  • Wickedly Fast: Native DOM performance, no virtual DOM overhead
  • Computed Properties: Auto-tracked, microtask-batched
  • Memory Savvy: No wasteful immutable data structures
  • Enterprise-Ready: Full CSP + Trusted Types compliance
  • Type-Safe: Full TypeScript support
  • Built-in Router: Path-based routing, guards, lazy loading
  • SSR Out of the Box: Server rendering, streaming, hydration
  • Reactive CSS Data: CSS custom properties as reactive state
  • CSS-Driven Animations: Exit transitions with zero JS code
  • Dev Mode: Verbose warnings for debugging with zero production overhead
  • Plays Well Everywhere: Works with any stack
  • Tidy: Automatic cleanup with configurable destruction delay
  • Open Source: MIT licensed, of course!

Give It a Spin

<script type="importmap">
  {
    "imports": {
      "@symbiotejs/symbiote": "https://esm.run/@symbiotejs/symbiote"
    }
  }
</script>

<script type="module">
  import Symbiote, { html } from '@symbiotejs/symbiote';

  export class MyComponent extends Symbiote {

    // Initialize state:
    init$ = {
      count: 0,
    }

    onIncrement() {
      this.$.count++;
    }

  }

  // Define template:
  MyComponent.template = html`
    <h2>{{count}}</h2>
    <button ${{onclick: 'onIncrement'}}>Click me!</button>
  `;

  // Register new tag name:
  MyComponent.reg('my-component');
</script>

Use the component anywhere in your HTML:
<my-component></my-component>

This little HTML example has everything you need to get a Symbiote.js app running. No build tools, no installation, no local server — just open it in a browser. And of course, you can use it with TypeScript, bundlers, linters, and all that good stuff.

Get your 100 points in Lighthouse

Want to Learn More?

Documentation Code Playground npm GitHub Community
11.12.2025
JSDA is very simple
Why JSDA is the best way to build web apps
28.09.2024
It was really possible?
Symbiote.js as an answer to many questions
17.09.2024
Smart HTML-tags
Simple recipe with the Artificial Intelligence
25.01.2024
Symbiote VS Lit
David and Goliath: differences, pros and cons...
18.01.2024
Symbiote.js 2.x
The new major version is released. Let's see what's new...
RND-PRO.com © 2026