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

shiva

v6.1.0

Published

Minimal JavaScript library for building user interfaces

Downloads

686

Readme

shiva

GitHub license npm version PRs Welcome javascript front-end declarative ui library

shiva is a minimal JavaScript library (2.5KB gzipped) for building reactive user interfaces - no template language, no virtual DOM. It is built on a simple premise: the DOM API is already a capable interface, what would a thin wrapper with a more ergonomic syntax look like?

So instead of:

<div>Hi there 👋</div>

shiva provides a function called div():

import { div } from "shiva"

const element = div("Hi there 👋")

Nested

import { div } from "shiva"

const nestedHTMLDivElement = div(
    div("Hi there 👋"),
    div("Hi there 👋"),
)

This will produce:

<div>
    <div>Hi there 👋</div>
    <div>Hi there 👋</div>
</div>

shiva will append HTML elements if they are passed as an argument. It will create and append a textNode element if a string is passed.

Mixed content

Strings and elements can be mixed freely as arguments. Order is preserved:

import { p, strong } from "shiva"

const sentence = p("The value is ", strong("42"), " and that's final.")

This produces:

<p>The value is <strong>42</strong> and that's final.</p>

Attributes

To pass attributes to an element:

import { div } from "shiva"

const handler = () => {
    console.log("handled it 👊")
}

const superDiv = div("Hi there 👋", { 
    onclick: handler,
    className: "mr-2",
    id: "Super Div 🦸‍♂️",
    style: {
        fontSize: "3rem"
    }
})

shiva will apply attributes if an object is passed as one of the arguments.

Elements

shiva provides:

  • div()
  • img()
  • p()
  • h1()
  • h2()
  • h3()
  • h4()
  • h5()
  • h6()
  • b()
  • strong()
  • i()
  • em()
  • a()
  • ul()
  • ol()
  • li()
  • span()
  • br()
  • code()
  • pre()
  • button()

To create other HTML elements not included in the API, shiva provides a more generic element() function.

import { element } from "shiva"

const section = element("section", "I am a section")

Any HTML element, including custom elements, can be created.

Components

Any function that returns a HTML element is a component:

import { div, h2, p } from "shiva"

const card = (title, body) => div(h2(title), p(body))

const page = div(
    card("Hello", "First card"),
    card("World", "Second card"),
)

Components are plain functions — no lifecycle, no decorators, no special syntax. Each component just needs to return an HTMLElement (or array of elements) to be composed into a parent.

Installation

npm install shiva

shiva is distributed as an ES module. Please use a module aware build tool to import.

See ./consumer for an example of how to include shiva in a project.

Getting started

Create root element

To append a root element to document.body, pass { root: true } as an option:

import { div } from "shiva"

const app = () => {
    div({ root: true },
        componentA(),
        componentB()
    )
}

app()

This appends a <div> to the page body with componentA and componentB nested inside.

👉 componentA and componentB must return a HTML element or array of HTML elements to be appended to the parent.

Reactive state with signals

shiva provides a signal factory for reactive state. A signal holds a value and notifies subscribers automatically when it changes.

import { signal, p, code } from "shiva"

const count = signal(0)

const counter = p(code(count))
// counter.textContent updates automatically when count changes

count.set(1) // counter now shows "1"

subscribe returns an unsubscribe function to stop listening:

const unsubscribe = count.subscribe(() => {
    console.log(count.get())
})

unsubscribe() // stops the listener

Object signals with reactive fields

When signal is given an object, every field becomes its own reactive signal. Pass field signals directly to elements - they update independently when only their value changes.

import { signal, h2, p } from "shiva"

const data = signal({ heading: "Hello", body: "World" })

const { heading, body } = data

const view = p(h2(heading), body)

data.set({ heading: "Updated", body: "World" })
// only the h2 updates - body is unchanged and does not re-render

Field signals can also be set individually, writing back to the parent signal:

heading.set("Just the heading")
// data.get().heading === "Just the heading"
// body is unaffected

Deeply nested reactive fields

Object signals are recursive. Nested objects also become reactive - field access chains as deep as the data structure:

import { signal, p } from "shiva"

const data = signal({
    meta: {
        title: "shiva",
        description: "A minimal UI library"
    }
})

const { meta } = data
const { title, description } = meta

const view = p(title, " - ", description)

data.set({
    meta: {
        title: "shiva",
        description: "No virtual DOM"
    }
})
// only description updates - title did not change

Writing back through a nested field signal propagates to the root:

description.set("Updated")
// data.get().meta.description === "Updated"

Reactive style

A signal can be passed as a style value to reactively update an element's styles.

import { signal, div } from "shiva"

const style = signal({ color: "crimson", fontSize: "2rem" })

const el = div("hello", { style })

style.set({ color: "royalblue", fontSize: "2rem" }) // el style updates in place

Individual style properties can also be signals:

const color = signal("crimson")

const el = div("hello", { style: { color } })

color.set("royalblue") // only color updates

Reactive attributes

The same patterns apply to attributes:

import { signal, div } from "shiva"

const attributes = signal({ "data-count": "0", id: "counter" })

const el = div("hello", { attributes })

attributes.set({ "data-count": "1", id: "counter" }) // attributes update in place

Individual attribute values can also be signals:

const dataCount = signal("0")

const el = div("hello", { attributes: { "data-count": dataCount } })

dataCount.set("1") // only data-count updates

Signal API

// Primitive signal
const s = signal(0)

s.get(): T
s.set(value: T): void
s.subscribe(fn: () => void): () => void  // returns unsubscribe

// Object signal - every field is also a signal
const s = signal({ name: "Alice", age: 30 }): ReactiveSignal<{ name: string, age: number }>

s.name.get()           // "Alice"
s.name.set("Bob")      // updates name, notifies name subscribers
s.name.subscribe(fn)   // subscribe to name changes only
s.get()                // { name: "Alice", age: 30 }
s.set({ name: "Bob", age: 30 }) // replace whole object

Note: The field names get, set, and subscribe are reserved and cannot be used as keys in objects passed to signal().

isSignal

A type guard for duck-typed signal detection:

import { signal, isSignal } from "shiva"

isSignal(signal(0))  // true
isSignal({ get: () => 0, set: () => {}, subscribe: () => () => {} })  // true
isSignal("hello")    // false

State (pubsub)

shiva also provides a simple publish / subscribe utility.

pubsub() returns an array where the first element is the subscribe function and the second element is the publish function.

import { pubsub } from "shiva"

const [subscribe, publish] = pubsub([1, 2, 4, 5])

subscribe(data => {
    console.log(data)
    // logs the value passed to publish()
})

// some time later
publish([6,7,9,12]) // logs [6,7,9,12]

API

pubsub<T>(
    initial?: T,
    reducer?: (current: T, next: T) => T
): [(callback: (state: T) => void) => void, (payload: T) => T]

Initialise state with initial.

Run a function on new state with a reducer function. Can be used for more complex logic or when the next state depends on the previous one.

const reducer = (state, newState) => state + newState

const [subscribe, publish] = pubsub("DOGE", reducer)

subscribe(state => {
    console.log(state)
})

publish(" to the moon 🚀") // logs "DOGE to the moon 🚀" 

Global store

Extending the publish / subscribe pattern across components creates a global store, useful if you don't want to pass subscribers and publishers through nested components.

shiva provides a createStore() function to create this global store.

// global-store.js
import { createStore } from "shiva"

const globalStore = createStore("🌍")

export default globalStore

Now subscribe or publish to this store.

// another file
import globalStore from "./global-store"

const [subscriberGlobal, publishGlobal] = globalStore

Here we can name the subscribe / publish functions, note we don't call the globalStore, it is already a subscribe / publish tuple.

TypeScript

shiva ships with type definitions. Types are available for import:

import { signal } from "shiva"
import type { Signal, ReactiveSignal, ReactiveStyle, ContainerOptions } from "shiva"

const count: Signal<number> = signal(0)

const style = signal<ReactiveStyle>({ color: "blue" })

type User = { name: string; age: number }
const user: ReactiveSignal<User> = signal({ name: "Alice", age: 30 })
// user.name is Signal<string>, user.age is Signal<number>

Tree shaking

shiva is marked as side-effect free. Bundlers like webpack, rollup, and vite will tree-shake unused exports.

License

MIT license