npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@symbiotejs/symbiote

v3.8.2

Published

Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components

Readme

Tests npm version npm downloads license

Symbiote.js

A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no black boxes, no excess repaints. No build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional.

Here are the three most important differences between Symbiote.js and other frameworks:

  1. Natural DOM Extension Philosophy - designed to extend platform APIs, not to replace them
  2. Runtime-Agnostic HTML Templates - outstanding flexibility for rendering strategies and further customization
  3. Powerful App-wide State Management - combine data contexts without bloated boilerplate or external tools

What's new in v3.x?

  • WebMCP support - expose live Symbiote UI actions as browser-native tools for agents. See the WebMCP docs.
  • Server-Side Rendering - render components to HTML with SSR.processHtml() or stream chunks with SSR.renderToStream(). Client-side hydration via ssrMode attaches bindings to existing DOM without re-rendering.
  • Isomorphic components - isoMode flag makes components work in both SSR and client-only scenarios automatically. If server-rendered content exists, it hydrates; otherwise it renders the template from scratch. One component, zero conditional logic.
  • Computed properties refined - reactive derived state with microtask batching.
  • Path-based router - optional AppRouter module with :param extraction, route guards, and lazy loading.
  • Exit animations - animateOut(el) for CSS-driven exit transitions, integrated into itemize API.
  • Dev mode - Symbiote.devMode enables verbose warnings; import devMessages.js for full human-readable messages.
  • DSD hydration - ssrMode supports both light DOM and Declarative Shadow DOM.
  • Class property fallback - binding keys not in init$ fall back to own class properties/methods.
  • Lazy mode - lazyMode flag defers component initialization and rendering based on viewport visibility. Can also be enabled via the lazy attribute on itemize containers to efficiently handle massive data sets.

Quick start

No install needed - run this directly in a browser:

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

  class MyCounter extends Symbiote {
    count = 0;
    increment() {
      this.$.count++;
    }
  }

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

  MyCounter.reg('my-counter');
</script>

<my-counter></my-counter>

Or install via npm:

npm i @symbiotejs/symbiote
import Symbiote, { html, css } from '@symbiotejs/symbiote';

Core concepts

Reactive state

class TodoItem extends Symbiote {
  text = '';
  done = false;
  toggle() {
    this.$.done = !this.$.done;
  }
}

TodoItem.template = html`
  <span ${{onclick: 'toggle'}}>{{text}}</span>
`;

State changes update the DOM synchronously. No virtual DOM, no scheduling, no surprises. And since components are real DOM elements, state is accessible from the outside via standard APIs:

document.querySelector('my-counter').$.count = 42;

This makes it easy to control Symbiote-based widgets and microfrontends from any host application - no framework adapters, just DOM.

Templates

Templates are plain HTML strings - runtime-agnostic, easy to test, easy to move between files:

// Separate file: my-component.template.js
import { html } from '@symbiotejs/symbiote';

export default html`
  <h1>{{title}}</h1>
  <button ${{onclick: 'doSomething'}}>Go</button>
`;

The html function supports two interpolation modes:

  • Object → reactive binding: ${{onclick: 'handler'}}
  • String/number → native concatenation: ${pageTitle}

Itemize (dynamic reactive lists)

Render lists from data arrays or objects with efficient updates:

class TaskList extends Symbiote {
  tasks = [
    { name: 'Buy groceries' },
    { name: 'Write docs' },
  ];
}

TaskList.template = html`
  <ul itemize="tasks">
    <template>
      <li>{{name}}</li>
    </template>
  </ul>
`;

Pop-up binding (^)

The ^ prefix works in any nested component template - it walks up the DOM tree to find the nearest ancestor that has the property registered in its data context (init$ or add$()):

<!-- Text binding to parent property: -->
<div>{{^parentTitle}}</div>

<!-- Handler binding to parent method: -->
<button ${{onclick: '^parentHandler'}}>Click</button>

Named data contexts

Share state across components without prop drilling:

import { PubSub, html } from '@symbiotejs/symbiote';

PubSub.registerCtx({
  user: 'Alex',
  theme: 'dark',
}, 'APP');

// Any component can read/write:
this.$['APP/user'] = 'New name';

// Any template can use property directly:
let template = html`<h2>{{APP/user}}</h2>`;

Shared context (*)

Inspired by native HTML name attributes - like how <input name="group"> groups radio buttons - the ctx attribute groups components into a shared data context. Components with the same ctx value share *-prefixed properties:

<upload-btn ctx="gallery"></upload-btn>
<file-list  ctx="gallery"></file-list>
<status-bar ctx="gallery"></status-bar>
class UploadBtn extends Symbiote {
  init$ = { '*files': [] }

  onUpload() {
    this.$['*files'] = [...this.$['*files'], newFile];
  }
}

class FileList extends Symbiote {
  init$ = { '*files': [] }
}

class StatusBar extends Symbiote {
  init$ = { '*files': [] }
}

All three components access the same *files state - no parent component, no prop drilling, no global store boilerplate. Just set ctx="gallery" in HTML and use *-prefixed properties. This makes it trivial to build complex component relationships purely in markup, with ready-made components that don't need to know about each other.

Application routing

// Import optional module:
import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';

AppRouter.initRoutingCtx('R', {
  home:    { pattern: '/' },
  profile: { pattern: '/user/:id' },
  about:   { pattern: '/about', lazyComponent: () => import('./about.js') },
});

CSS Styling

Shadow DOM is optional in Symbiote - use it when you need isolation, skip it when you don't. This gives full flexibility:

Light DOM - style components with regular CSS, no barriers:

MyComponent.rootStyles = css`
  my-component {
    display: flex;
    gap: 1rem;

    & button { color: var(--accent); }
  }
`;

This style will be applied to nearest upper shadow root, if exists and to common document if not.

Shadow DOM - opt-in isolation when needed:

class Isolated extends Symbiote {}

Isolated.shadowStyles = css`
  :host { display: block; }
  ::slotted(*) { margin: 0; }
`;

All native CSS features work as expected: CSS variables flow through shadow boundaries, ::part() exposes internals, modern nesting, @layer, @container - no framework abstractions in the way. Mix light DOM and shadow DOM components freely in the same app.

CSS Data

Components can read CSS custom property values to initiate reactive state:

my-widget {
  --label: 'Click me';
}
class MyWidget extends Symbiote {...}

MyWidget.template = html`
  <span>{{--label}}</span>
`;

Best for

  • Complex widgets embedded in any host application
  • Low-code HTML-based solutions - simple declarative everything
  • Micro frontends - standard custom elements, no framework coupling
  • Reusable component libraries - works in React, Vue, Angular, or plain HTML
  • SSR-powered apps - lightweight server rendering without framework lock-in
  • Framework-agnostic solutions - one codebase, any context
  • Modern AI-first web - expose the application state to WebMCP tools automatically

Docs & Examples

Related articles

Questions or proposals? Welcome to Symbiote Discussions! ❤️


© rnd-pro.com - MIT License