n nisli
GitHub
The reactive web-component framework

Reactive web components. No build step, no virtual DOM.

nisli is a reactive framework for the native platform — signals, tagged-template components, dependency injection, static rendering. Batteries included: @nisli/ui, a shadcn-style component library you copy in and own.

counter.ts
import { component, signal, html } from '@nisli/core';

const Counter = component('my-counter', () => {
  const count = signal(0);

  return html`
    <button @click=${() => count.value++}>
      count is ${count}
    </button>
  `;
});

A framework, not a library of workarounds

Everything you need to build reactive interfaces on the web platform — compiled to nothing.

Signals

Fine-grained reactivity — signal(), computed(), effect(). No re-renders, no diffing; only what changed updates.

Tagged-template components

Author UI with the html`` tag. No JSX, no compiler, no build step — templates are just JavaScript.

Native custom elements

component() defines a real custom element. Interoperable everywhere, framework-agnostic, no runtime lock-in.

Dependency injection

provide() / inject() with typed tokens. Share services down the tree without prop-drilling or globals.

Declarative data

query() loads async data with caching and loading state, wired straight into the reactivity graph.

Static rendering

@nisli/ssg renders components to static HTML — this very site is nisli, rendered to a static bundle.

Install the framework, copy in the components

nisli = framework + design language + UI components, all in one. No registry account, no lock-in — the components become your source.

  1. 1

    Install the framework

    nisli is the product — signals, components, DI, all in @nisli/core.

    npm install @nisli/core
  2. 2

    Set up @nisli/ui

    One-time init drops the token layer and config into your project.

    npx @nisli/ui init
  3. 3

    Copy in the components you want

    add copies real source into your repo — you own and edit it.

    npx @nisli/ui add button dialog

Then import your copy and go:

import { Button } from '@/components/ui/button';

// your owned copy — edit it, it's yours
document.body.append(Button({ children: 'Ship it' }));