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

asciileton

v1.1.0

Published

React skeleton loader based on a simple string draft language

Readme

Asciileton – React SVG Skeleton Loader

Overview

Asciileton is a tiny React component that turns a draft string – a custom, human‑readable language – into a set of SVG‑styled skeleton blocks (squares or circles). The component parses the draft, expands groups and repetitions, and renders the corresponding <div> elements with Tailwind‑like dimensions and a pulsing animation.


Draft language syntax

The draft language is deliberately simple – it describes a vertical stack of placeholder blocks. Each block can be a square or a circle, may have an optional width, a required height, and can be repeated or grouped.

| Symbol | Meaning | |--------|---------| | [ … ] | Square block | | ( … ) | Circle block | | -N- | Aspect‑ratio mode (inside brackets). A single number defines the size in shorthand units (multiplied by 10, same rule as width). Width and height are both set to that value, so the block is always a perfect square (50 → 50×50, 60 → 60×60, etc.). The shape is decided by the surrounding brackets. Empty - means “use the default size (60)”. No x is used, which is the whole point of this shorthand. | | --…-- | Width × height mode (legacy, inside brackets). See width / height rows below for the inner syntax. | | width | Inside --…--: optional width. If omitted the default is a random integer in [10, 90]% (digit form [1, 9] with shorthand applied). A single number (50) is a fixed percentage. A closed range (20..60) and an open range (5.. → [50, 90], ..3 → [10, 30]) are also supported. Shorthand: any single‑digit width value (< 10) — including the default bounds 1 and 9 — is multiplied by 10 (e.g. 5 → 50%, 3..7 → 30..70%, ..3 → 10..30%). | | start..end | Optional width expressed as an inclusive range (e.g. 20..60). At render time a random integer between start and end is picked and used as a percentage. Mutually exclusive with width. | | x | Separator between width and height inside the legacy --…-- syntax. Not used in the new -N- mode. | | height | Height expressed as pixels (same shorthand as width). A single‑digit value (< 10) is multiplied by 10 — e.g. 5 → 50px, 9 → 90px; a two‑digit value is taken as‑is — e.g. 22 → 22px, 100 → 100px. The number goes straight into the CSS height (no Tailwind‑scale multiplier). | | start..end | Optional height expressed as an inclusive range in the same shorthand (e.g. 1..6 → [10, 60]px, 5..22 → [50, 22]px clamped at render time). At render time a random integer between the two widened values is picked and used as the pixel height. Mutually exclusive with height. | | (space) | Default horizontal separator. A single space (or any whitespace run) between two elements is treated as | — elements sit on the same line with the default horizontal margin. Use this when you just want the elements side‑by‑side. | | + or +N | Forces the next block to start on a new line. Without a number the margin‑top is 16px (2 × 8px); with a number N the margin‑top becomes N × 8px. | | \| or \|N | Horizontal separator — the next block stays on the same line but gains a left margin. Without a number the margin is 16px (2 × 8px); with a number N it becomes N × 8px. | | = or =N | Center alignment — the next block stays on the same line and gains a left margin (default 16px, N × 8px), and forces the entire row to be centered horizontally. | | *N | Repeats the preceding element N times (e.g. (-6-)*3 or [--x6--]*3). The copies are placed on the same line (horizontal row) — use +*N if you want a vertical stack instead. Has higher precedence than +, so [--a--]+[--b--]*10 produces 11 elements (1 + 10), not 11 copies of the sum. | | +*N | Repeats the last element N times with a line break (margin‑top) between each copy (e.g. (-6-)+*10 produces 10 copies of the circle, each on its own line). Combines the meaning of + and *N in one operator. | | ( … )*N | Group – a sequence of blocks containing at least one + (line break). After expansion the group is inserted N times, with each repetition separated by a +. The group regex requires + inside, so (-6-)*10 is treated as a single element with repetition (not a group). |

Layout

  • Elements without any separator sit horizontally (side‑by‑side in a single row). (-4-)*3 draws O O O, [--3x5--]*3 draws ▢ ▢ ▢.
  • + (or +N) breaks the row — the next element starts on a new line with a vertical margin.
  • | (or |N) and a single space between elements add a horizontal margin within the same row.
  • = (or =N) centers the row — all elements in the current line are centered horizontally.

Grammar (informal)

Draft       ::= Item ( "+" Item )*
Item        ::= Block | Group
Block       ::= Square | Circle
Square      ::= "[" Size "--]"
Circle      ::= "(" Size "--)"
Size        ::= "--" [Width "%"] ["x"] Height "--"
Width       ::= DIGITS
Height      ::= DIGITS
Group       ::= "(" Draft ")" ["*" Number]
Number      ::= DIGITS
  • Width is optional. If omitted the default is a random integer in [10, 90]% (digit form [1, 9] after the shorthand). It can be a single number (50), a closed range (20..60), or an open range (5.. → [50, 90], ..3 → [10, 30]). Shorthand: any single‑digit width value (< 10) — including the default bounds 1 and 9 — is multiplied by 10 — 5 → 50, 3..7 → 30..70, 5.. → 50..90.
  • Height is mandatory and determines the vertical size (height * 8px). It can be either a single number (6) or a range (1..6).
  • The + token does not create a visual element; it adds a top margin to the following block. If written as + the margin is 24px; +N sets the margin to N × 8px.
  • Repetition (*N) expands the element before rendering – the parser creates N identical SkeletonItem objects.

Examples

import Asciileton from "./Asciileton"

// Simple square, full width, height = 6 (=> 48px)
<Asciileton draft="[--x6--]" />

// Circle with explicit width 30 and height 4
<Asciileton draft="(--30x4--)" />

// Circle with a random width between 20 and 60 percent
<Asciileton draft="(--20..60x4--)" />

// Square with a random height between 1 and 6 units (8..48px)
<Asciileton draft="[--x1..6--]" />

// Same range as `[--30..70x6--]` thanks to the shorthand for values < 10
<Asciileton draft="[--3..7x6--]" />

// No width → random width in [1, 9]%
<Asciileton draft="[--x6--]" />

// Open upper bound → range [5, 9]%
<Asciileton draft="[--5..x6--]" />

// Open lower bound → range [1, 3]%
<Asciileton draft="[--..3x3--]" />

// Two squares on separate lines, second one is 50% wide
<Asciileton draft="[--x6--]+[--50x6--]" />

// Repeating a block 3 times
<Asciileton draft="[--x6--]*3" />

// Aspect‑ratio mode (new): a single number defines the size, no `x`.
// `-6-` → 60px radius shorthand, so 60×60 circle.
<Asciileton draft="(-6-)" />

// Same idea for a square — 60×60 (no rounded corners)
<Asciileton draft="[-6-]" />

// Empty body uses the default size (60)
<Asciileton draft="(--)" />

// Horizontal layout: 3 circles in a single row
<Asciileton draft="(-4-)*3" />

// Same for the legacy syntax: 3 squares side-by-side
<Asciileton draft="[--3x5--]*3" />

// Whitespace between elements is the default horizontal separator
<Asciileton draft="(-3-) (-4-)" />

// Explicit `|` for horizontal spacing, `|N` for custom spacing
<Asciileton draft="(-3-)|3(-4-)" />

// Centered elements in a row
<Asciileton draft="(-6-)=(-6-)" />

// `+` forces a new line (default margin 16px), `+N` for custom
<Asciileton draft="(-6-)+3[-6-]" />

// Combinations
<Asciileton draft="(-12-)+[-6-]*3" />

// Group with line‑breaks repeated 2 times
<Asciileton draft="([--x6--]+[--30x6--])*2" />

The last example expands to:

[--x6--] + [--30%x6--] + [--x6--] + [--30%x6--]

resulting in four squares, each pair separated by a vertical margin.


Installation

npm install asciileton --save-peer react

Usage

import { Asciileton } from "asciileton"

function MyComponent() {
  const draft = "(--30x6--)+[--50x4--]*2"
  return <Asciileton draft={draft} className="w-full" />
}
// Asciileton.tsx is exported as default
import Asciileton from "./Asciileton"

function ProfileLoading() {
  const draft = "(--30x6--)+[--50x4--]*2"
  return <Asciileton draft={draft} className="w-full" />
}

You can style the outer wrapper with the optional className prop or wrap the component in any layout container.


How the parser works (quick overview)

  1. Pre‑processing – whitespace around operators +, |, * is stripped. Any whitespace run between a closing bracket () or ]) and an opening bracket (( or [) is replaced by a single |. Remaining whitespace is removed. The result is equivalent to the user having written | between elements that were separated by a space.
  2. Group expansion – the regex /\(([^()]*\+[^()]+)\)\*(\d+)/g repeatedly replaces (content)*N with content+content+… (N times). The regex requires + inside the parens, so (-6-)*10 is not treated as a group – it stays as a single element that is later repeated by the per‑element *N logic.
  3. Tokenisation – the string is split on +, |, * and the bracket boundaries, preserving each element token.
  4. Element parsing – each token that matches ^[\[\(].+?[\]\)]$ is interpreted:
    • Aspect‑ratio mode (new) – if the body matches ^[([](-(\d+)?-)[)\]]$, a single number sets radius (in shorthand‑pixels). Width and height are both equal to the radius. The shape comes from the brackets (( → circle, [ → square).
    • Legacy mode – otherwise the body is matched against --…WIDTHxHEIGHT…--, yielding optional width (single 50, closed range 20..60, open ranges 5.. / ..3, or default [10, 90]) and mandatory height (6 or 1..6).
    • + (or +N) sets marginTop of the next element and starts a new line; |N sets marginLeft of the next element (default 16px, N × 8px otherwise). The number N is consumed from the following token, so +N*M works too.
    • A trailing *N repeats the element N times on the same line.
  5. Rendering – items are grouped into rows by marginTop (each row is a flex‑row container; the first row has no top margin, subsequent rows use the first item’s marginTop). Within a row, marginLeft provides horizontal spacing. If radius is set: width: height: radius, border-radius: 9999px for circles or none for squares. Otherwise: height: <units> * 8px, width: <resolved>%, border-radius: 9999px for circles or none for squares. A static @keyframes pulse animation is applied.

License

MIT – feel free to copy, modify, and publish.