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

@agilek/cli-loaders

v1.1.0

Published

Braille unicode spinners as React decorator components

Readme

cli-loaders for react

Animated braille-glyph loading spinners for React. 19 unique animations, zero dependencies, fully accessible. — Live demo → · GitHub →

cli-loaders gives you 19 animated braille-glyph spinners as a set of composable, accessible React components. Zero runtime dependencies beyond React itself.

Features

  • 19 spinner animations with unique braille Unicode patterns
  • Zero runtime dependencies — pure React, no external libraries
  • Fully accessible — WCAG 2.1 AA, prefers-reduced-motion support, semantic HTML
  • TypeScript first — complete type definitions, DotShape export for custom rendering
  • Composable — 7 components + 2 hooks for any use case
  • Small bundle — ~15KB gzipped (lib) + optional SVG rendering
  • Shape customization — render as circles, squares, or diamonds (SVG mode)

Why cli-loaders?

vs. react-spinners: Smaller bundle, braille Unicode art for terminal-like aesthetics, context API for global defaults, shape customization (circles/squares/diamonds)

vs. react-loading: Zero dependencies, comprehensive accessibility testing, 19+ unique animations, SVG rendering mode

vs. custom CSS: No @keyframes to maintain, out-of-the-box reduced-motion support, semantic HTML, accessible by default

Install

npm install @agilek/cli-loaders
# or
pnpm add @agilek/cli-loaders
# or
yarn add @agilek/cli-loaders

Requires React ≥ 18.

Quick start

import { Spinner } from '@agilek/cli-loaders';

// Minimal — defaults to the "braille" spinner
<Spinner />

// Named + coloured
<Spinner name="helix" color="#7c3aed" size="1.5rem" />

// With custom dot shape (renders as SVG instead of braille text)
<Spinner name="scan" color="#00ff99" shape="square" size="2rem" />

Components

<Spinner>

The base animated glyph. Renders as an inline <span> so it drops naturally into any line of text.

| Prop | Type | Default | Description | |---|---|---|---| | name | SpinnerName | "braille" | Which spinner animation to use | | color | string | currentColor | CSS color value | | size | string \| number | undefined | Font size of the glyph, e.g. "1.5rem" or 24 | | speed | number | 1 | Playback multiplier — 2 = twice as fast | | paused | boolean | false | Freeze the animation | | ignoreReducedMotion | boolean | false | Override prefers-reduced-motion and always animate | | shape | "circle" \| "square" \| "diamond" | undefined | Render dots as SVG shapes instead of braille text | | label | string | "Loading" | Accessible label announced by screen readers | | className | string | — | Applied to the outer <span> | | style | CSSProperties | — | Inline styles for the outer <span> | | ref | Ref<HTMLSpanElement> | — | Forwarded to the outer <span> |

Any additional HTML span attributes are forwarded.


<SpinnerInline>

Spinner + children in a flex row — handy for inline status messages.

<SpinnerInline name="braille" color="#00ff99">
  Fetching data…
</SpinnerInline>

Accepts all Spinner props plus:

| Prop | Type | Default | Description | |---|---|---|---| | gap | string \| number | "0.4em" | Gap between spinner and children | | children | ReactNode | — | Content rendered after the spinner |


<SpinnerText>

Decorates a text string with spinners — supports a bookend mode that mirrors one on each end.

<SpinnerText text="Deploying" color="#f59e0b" bookend />
// ⠿ Deploying ⠿

| Prop | Type | Default | Description | |---|---|---|---| | text | string | required | The text to decorate | | bookend | boolean | false | Place a spinner on both sides | | gap | string \| number | "0.4em" | Gap between spinners and text |

Plus all Spinner props.


<SpinnerBadge>

A tinted pill badge with a spinner — good for live status indicators.

<SpinnerBadge label="Live"     color="#ef4444" />
<SpinnerBadge label="Syncing"  color="#38bdf8" />
<SpinnerBadge label="Building" color="#f59e0b" />

| Prop | Type | Default | Description | |---|---|---|---| | label | string | required | Badge text (also used as the a11y label) | | paddingY | string | "0.4em" | Vertical padding | | paddingX | string | "0.8em" | Horizontal base padding | | balanceRatio | number | 0.3 | Extra right padding fraction for optical balance | | borderRadius | string \| number | "999px" | Border radius | | gap | string \| number | "0.35em" | Gap between glyph and label |

Plus all Spinner props.


<SpinnerTrail>

Renders a sliding window of recent frames with decreasing opacity — creates a ghost/motion-blur trail effect.

<SpinnerTrail name="helix" color="#7c3aed" size="2rem" trailLength={6} />

| Prop | Type | Default | Description | |---|---|---|---| | trailLength | number | 4 | Number of ghost frames (including the current one) | | minOpacity | number | 0.1 | Opacity of the oldest ghost frame | | reverse | boolean | false | Flip the opacity gradient (bright on left, fading right) |

Plus all Spinner props.


<SpinnerButton>

A <button> with a built-in loading state. Disables itself and shows a spinner when loading is true. Fully forwarded ref.

<SpinnerButton
  loading={isSaving}
  onClick={handleSave}
  spinnerProps={{ name: 'braille', color: '#00ff99' }}
>
  Save changes
</SpinnerButton>

| Prop | Type | Default | Description | |---|---|---|---| | loading | boolean | false | Show spinner and disable the button | | spinnerPosition | "left" \| "right" | "left" | Which side the spinner appears on | | spinnerGap | string \| number | "0.45em" | Gap between spinner and button label | | spinnerProps | Omit<BaseSpinnerProps, "size"> | — | Props forwarded to the inner <Spinner> |

All standard <button> attributes are forwarded.


<SpinnerOverlay>

Wraps any content and renders a centered spinner overlay when active. Uses the native inert attribute to block keyboard/pointer access to the content beneath.

<SpinnerOverlay
  active={isLoading}
  name="orbit"
  color="#7c3aed"
  size="2.5rem"
  backdrop="rgba(0,0,0,0.4)"
>
  <YourContent />
</SpinnerOverlay>

| Prop | Type | Default | Description | |---|---|---|---| | active | boolean | true | Show the overlay | | backdrop | string | "rgba(0,0,0,0.35)" | CSS background for the backdrop | | size | string \| number | "2rem" | Spinner glyph size | | containerStyle | CSSProperties | — | Style applied to the outer wrapper <div> | | containerClassName | string | — | Class applied to the outer wrapper <div> | | children | ReactNode | — | Content that gets overlaid |

Plus all Spinner props.


<SpinnerProvider>

Sets global defaults for every Spinner-family component in the subtree. Individual props override context values.

<SpinnerProvider
  defaultName="orbit"
  defaultColor="#7c3aed"
  defaultSpeed={1.2}
  respectReducedMotion={true}
>
  <App />
</SpinnerProvider>

// Inside: no props needed — picks up context defaults
<Spinner />
<SpinnerBadge label="Live" />

| Prop | Type | Default | Description | |---|---|---|---| | defaultName | SpinnerName | "braille" | Default spinner name | | defaultColor | string | undefined | Default color | | defaultSize | string \| number | undefined | Default glyph size | | defaultSpeed | number | 1 | Default speed multiplier | | defaultShape | "circle" \| "square" \| "diamond" | undefined | Default dot shape | | respectReducedMotion | boolean | true | Pause animations when the OS has prefers-reduced-motion: reduce |


useSpinner hook

Drive any element with the raw frame string for fully custom rendering.

import { useSpinner } from 'cli-loaders';

function MyComponent() {
  const frame = useSpinner('helix', 1.5);

  return (
    <h1 style={{ fontFamily: 'monospace', color: '#00ff99' }}>
      {frame}
    </h1>
  );
}
useSpinner(
  name: SpinnerName,
  speed?: number,           // default 1
  paused?: boolean,         // default false
  ignoreReducedMotion?: boolean  // default false
): string

useSpinnerFrames

Returns a sliding window of the last length frames — the primitive behind SpinnerTrail.

useSpinnerFrames(
  name: SpinnerName,
  length?: number,          // default 3
  speed?: number,
  paused?: boolean,
  ignoreReducedMotion?: boolean
): string[]

Available spinners

All 19 names are available as the SpinnerName union type.

import { spinnerNames } from 'cli-loaders';
// ['braille', 'braillewave', 'dna', ...]

Accessibility

  • The animated glyph is wrapped in aria-hidden="true" — it is invisible to screen readers.
  • A visually-hidden <span role="status" aria-live="polite"> announces the label prop (default: "Loading").
  • SpinnerButton sets aria-busy and aria-disabled on the button element automatically.
  • SpinnerOverlay sets aria-busy on the container and uses the native inert attribute to prevent keyboard/AT access to obscured content.
  • By default, all animations respect prefers-reduced-motion: reduce. Override per-component with ignoreReducedMotion or globally via <SpinnerProvider respectReducedMotion={false}>.


FAQ

Can I use this with Next.js? Yes. Works as both client and server components in the App Router.

How do I customize spinner animations? Use the useSpinner hook to drive any custom rendering. See useSpinner hook for examples.

Does this work with TypeScript? Full type support. All component props and the DotShape type are exported for type safety.

How do I change shapes (circles, squares, diamonds)? Use the shape prop: <Spinner shape="square" />. See Spinner props for details.

Does this support reduced motion preferences? Yes, by default. Animations pause when prefers-reduced-motion: reduce is set. Override with ignoreReducedMotion={true}.

What's the bundle size? ~15KB gzipped for the core library. SVG rendering mode adds ~1KB.


Common Patterns

Form submission with loading state

const [loading, setLoading] = useState(false);

async function handleSubmit() {
  setLoading(true);
  try {
    await api.post('/submit', data);
  } finally {
    setLoading(false);
  }
}

return (
  <SpinnerButton
    loading={loading}
    onClick={handleSubmit}
    spinnerProps={{ name: 'helix', color: '#00ff99' }}
  >
    {loading ? 'Submitting...' : 'Submit'}
  </SpinnerButton>
);

Full-screen loading overlay

const [fetching, setFetching] = useState(false);

return (
  <SpinnerOverlay
    active={fetching}
    name="orbit"
    size="2rem"
    backdrop="rgba(0,0,0,0.5)"
  >
    <Dashboard data={data} />
  </SpinnerOverlay>
);

Inline status message with shape customization

return (
  <SpinnerInline
    name="scan"
    shape="diamond"
    color="#7c3aed"
  >
    Syncing your data...
  </SpinnerInline>
);

Contributing

Bug reports and pull requests are welcome. Please open an issue before submitting large changes.

# Install dependencies
npm install

# Start the interactive demo
npm run dev

# Build the library
npm run build

License

MIT