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

not-a-spinner

v0.2.1

Published

Because modern AI doesn't spin, it thinks. Replace loading spinners with AI-style rotating thinking phrases.

Readme

not-a-spinner

Because modern AI doesn't spin, it thinks.

A lightweight React component that replaces loading spinners with rotating AI-style thinking phrases. Ships with 210+ built-in phrases across 7 languages, 4 animation styles, and optional LLM-generated messages.

Live Demo

Install

npm install not-a-spinner
# or
yarn add not-a-spinner
# or
pnpm add not-a-spinner

Quick Start

1. Import the CSS once (in your root layout or entry point):

import "not-a-spinner/styles.css"

2. Use the component:

"use client"
import { NotASpinner } from "not-a-spinner"

export function MyLoader() {
  return <NotASpinner />
}

That's it. You get rotating English phrases with a fade animation and pulsing dots.

Examples

// Different animations
<NotASpinner animation="fade" />       // crossfade (default)
<NotASpinner animation="typewriter" />  // character-by-character with cursor
<NotASpinner animation="slide" />       // slide up transition
<NotASpinner animation="blur" />        // blur in/out

// Sizes
<NotASpinner size="sm" />
<NotASpinner size="default" />
<NotASpinner size="lg" />

// Languages (30 phrases each)
<NotASpinner locale="en" />  // English (default)
<NotASpinner locale="zh" />  // 中文
<NotASpinner locale="ja" />  // 日本語
<NotASpinner locale="es" />  // Español
<NotASpinner locale="fr" />  // Français
<NotASpinner locale="de" />  // Deutsch
<NotASpinner locale="ko" />  // 한국어

// Custom messages
<NotASpinner messages={["Crunching numbers", "Consulting the oracle", "Almost there"]} />

// Disable dots
<NotASpinner dots={false} />

// Faster rotation (ms)
<NotASpinner interval={1500} />

// Combine options
<NotASpinner animation="typewriter" locale="ja" size="lg" dots={false} />

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | animation | "fade" \| "typewriter" \| "slide" \| "blur" | "fade" | Text transition style | | size | "sm" \| "default" \| "lg" | "default" | Font size | | locale | "en" \| "zh" \| "ja" \| "es" \| "fr" \| "de" \| "ko" | "en" | Built-in phrase language | | messages | string[] | — | Custom phrases (overrides locale) | | fetchPhrase | () => Promise<string> | — | Async phrase fetcher for LLM integration | | interval | number | 3000 | Rotation speed in ms | | dots | boolean | true | Show trailing ... | | className | string | — | Additional CSS classes (merged via cn()) |

LLM-Generated Phrases

Generate fresh phrases on the fly using OpenAI or Anthropic.

1. Create a server route:

// app/api/thinking/route.ts
import { createThinkingHandler } from "not-a-spinner/server"

const handler = createThinkingHandler({
  provider: "openai",          // or "anthropic"
  apiKey: process.env.OPENAI_API_KEY!,
  model: "gpt-4o-mini",       // optional
  locale: "en",               // optional
})

export const POST = handler

2. Connect to the component:

"use client"
import { NotASpinner, createOpenAIFetcher } from "not-a-spinner"

const fetchPhrase = createOpenAIFetcher("/api/thinking")

export function MyLoader() {
  return <NotASpinner fetchPhrase={fetchPhrase} animation="typewriter" />
}

The component starts with built-in phrases immediately and mixes in LLM-generated ones as they arrive. If the API fails, it silently falls back to local phrases.

Custom Prompt

const handler = createThinkingHandler({
  provider: "anthropic",
  apiKey: process.env.ANTHROPIC_API_KEY!,
  systemPrompt: `You generate short, sarcastic loading messages for a developer tool.
Keep it under 40 characters. Be dry and witty. Output only the phrase.`,
})

Built-in Default Prompt

The default prompt instructs the LLM to generate short, funny, nerdy loading messages (under 50 characters) matching the tone of phrases like "Reticulating splines" and "Downloading more RAM". It auto-appends locale-specific instructions with example phrases in each language.

Bring Your Own Fetcher

Skip the server helper entirely — pass any () => Promise<string>:

<NotASpinner
  fetchPhrase={async () => {
    const res = await fetch("/your-own-api")
    const data = await res.json()
    return data.text
  }}
/>

Styling

The component ships pre-compiled CSS with nas- prefixed classes — no Tailwind required. But if you use Tailwind, you can override styles via className:

<NotASpinner className="text-blue-500 text-xl" />

To customize the base color globally, set the CSS variable:

:root {
  --nas-color: #8b5cf6;
}

Sample Phrases

English: "Reticulating splines", "Asking the rubber duck", "Downloading more RAM", "Bribing the cache fairy"

中文: "让AI再想想", "正在炼丹", "正在解开薛定谔的Bug", "偷偷查看答案中"

日本語: "AIが悩んでいます", "ピクセルを磨き中", "もうちょっと待ってね"

한국어: "AI가 고민 중", "바쁜 척하는 중", "거의 거의 다 됐어요"

Each locale includes 30 curated phrases.

Requirements

  • React 18+
  • For LLM features: OpenAI or Anthropic API key

License

MIT