not-a-spinner
v0.2.1
Published
Because modern AI doesn't spin, it thinks. Replace loading spinners with AI-style rotating thinking phrases.
Maintainers
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.
Install
npm install not-a-spinner
# or
yarn add not-a-spinner
# or
pnpm add not-a-spinnerQuick 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 = handler2. 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
