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

nanosplash

v4.1.5

Published

The tiny loading screen for web artisans

Readme

Why Nanosplash

  • ~2.6 kB gzipped. Smaller than most icons. Nothing else to ship.
  • Zero dependencies. Plain TypeScript, plain CSS, injected once on first use.
  • Framework-agnostic. Works in vanilla JS, React, Vue, Svelte, Angular, or any plain <script> tag.
  • Three-call API. show(), hide(), version. Nothing to learn, nothing to configure.
  • Fullscreen or scoped. Overlay the whole page or any element you pass in, by selector or reference.
  • Labeled async jobs. Hand show() your promises. The label updates per step and the splash hides itself when done.
  • Anti-flicker timing. Optional showDelay and minDuration so fast requests never blink a spinner.
  • Accessible and themeable. role="status", aria-live, reduced-motion support, automatic dark mode, and --ns-* CSS custom properties.
  • ESM, CJS, and IIFE builds with bundled TypeScript types.

Install

npm install nanosplash

Or drop it in from a CDN with no build step. This exposes a global useNs:

<script src="https://unpkg.com/nanosplash/dist/iife/ns.iife.js"></script>

Quick start

import { useNs } from 'nanosplash'

const ns = useNs()

ns.show('Loading')
// ...do work...
ns.hide()

Usage

Fullscreen or scoped to an element

ns.show() // fullscreen spinner, no text
ns.show('Loading') // fullscreen spinner with a label
ns.show('Fetching data', '#dashboard') // inside an element, by CSS selector
ns.show('Please wait', document.querySelector('.card')) // by element reference

Calling show() again on the same container updates the label instead of stacking a second spinner. The fullscreen splash never touches the page's scroll position.

Run work under a splash

Pass a [label, job] pair and the splash lives exactly as long as the job. Pass several pairs and they run in order under one splash, with the label updating as each step starts. Results come back as a typed tuple.

const user = await ns.show(['Loading user', () => fetchUser()])

const [profile, posts] = await ns.show([
  ['Loading user', () => fetchUser()],
  ['Loading posts', () => fetchPosts()],
])

Rejections propagate after the splash is hidden, so a normal try/catch works.

Hide

ns.hide() // hide the oldest active splash
ns.hide(id) // hide one specific splash, using the id returned by show()
ns.hide('*') // hide every active splash

Anti-flicker timing

Configure once and every splash from that instance follows the same rules. The splash stays invisible for showDelay ms and, once visible, stays for at least minDuration ms. Requests that finish inside the delay never show a spinner at all.

const ns = useNs({ showDelay: 150, minDuration: 400 })

Theming

Nanosplash ships light and dark defaults that follow prefers-color-scheme, with a data-theme="light|dark" attribute on the root element as an override. All defaults are declared at zero specificity, so any rule of yours wins regardless of load order.

:root {
  --ns-color: tomato;
  --ns-size: 24px;
  --ns-bg: rgba(0, 0, 0, 0.8);
}

/* Theme only the loaders inside one panel */
#dashboard {
  --ns-color: white;
  --ns-bg: rgba(0, 0, 0, 0.6);
}

| Property | Default | Controls | | ------------------- | ------------------------------- | ------------------------------------- | | --ns-color | DarkSlateGray | Spinner and text color | | --ns-size | 20px | Base size; the spinner scales from it | | --ns-font | 'Inter', 'Helvetica', 'Arial' | Label font stack | | --ns-weight | 400 | Label font weight | | --ns-bg | rgba(255, 255, 255, 0.9) | Overlay background | | --ns-blur | blur(5px) | Backdrop blur | | --ns-shadow-color | rgba(0, 0, 0, 0.25) | Text and spinner shadow | | --ns-z-index | 9999999999 | Overlay z-index |

Accessibility

  • The splash has role="status" and aria-live="polite", so the label is announced when it appears or changes.
  • The host container gets aria-busy="true" while a splash is active and loses it on hide.
  • Under prefers-reduced-motion: reduce, fades and entrance animations are disabled and the spinner rotates slowly with a static arc.

API at a glance

| Call | Returns | Description | | ---------------------------------- | ------------------------- | --------------------------------------------------------------------------- | | useNs(options?) | { show, hide, version } | Create an API instance. options sets showDelay and minDuration. | | show(label?, inside?) | number \| null | Show a splash. Returns its id, or null if inside could not be resolved. | | show([label, job], inside?) | Promise<T> | Run one job under a splash and resolve with its result. | | show([[label, job], …], inside?) | Promise<[T1, T2, …]> | Run jobs sequentially under one splash and resolve with a tuple. | | hide(id?) | void | Hide the oldest splash, a specific id, or all with '*'. | | version | string | The installed Nanosplash version. |

inside accepts an Element or a CSS selector string. See the full documentation for every detail, including instance recycling, FIFO ordering, and host classes.

Module formats

| Format | Entry | Use | | ------ | ---------------------- | ----------------------------------------------------- | | ESM | dist/es/ns.es.js | import { useNs } from 'nanosplash' | | CJS | dist/cjs/ns.cjs.js | const { useNs } = require('nanosplash') | | IIFE | dist/iife/ns.iife.js | <script> tag from unpkg or jsDelivr, global useNs |

Types ship in the package, so editors pick them up automatically.

Contributing

Found a bug or want a feature? Open an issue or send a pull request.

License

MIT © Isak Hauge