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

@c-hinck10/splitflap-js

v0.2.0

Published

A lightweight split-flap display for the web.

Downloads

1,440

Readme

@c-hinck10/splitflap-js

A lightweight split-flap display for the web.

It is built as a framework-agnostic DOM core with a tiny React wrapper, so the package stays embeddable and easy to extend to more frameworks later.

Features

  • Fixed-grid split-flap board
  • Word-aware row layout that avoids splitting words when possible
  • Long-message pagination into multiple board pages
  • Advanced pages API for row offsets, columns, decorators, and tones
  • Face-wheel model for authentic flap progression with optional custom ordering
  • DOM-driven animation core
  • load, visible, and manual trigger modes
  • Optional autoplay page rotation
  • Imperative controls like setMessage(), play(), next(), and reset()
  • Themeable via CSS variables
  • React wrapper that mounts the core without managing per-tile React state

Install

npm install @c-hinck10/splitflap-js

Local Demo

npm install
npm run dev

That opens a small playground from demo/ so you can tune timing, trigger behavior, and message changes against the real DOM animation.

Vanilla Usage

import { Flipboard } from '@c-hinck10/splitflap-js';
import '@c-hinck10/splitflap-js/styles.css';

const board = new Flipboard(document.getElementById('board')!, {
  size: '3x15',
  align: 'center',
  preserveWords: true,
  trigger: 'visible',
  autoplay: true,
  pageDuration: 3500,
  staggerMode: 'simultaneous',
  messages: [
    'WELCOME TO THE HILTON BEAVER CREEK, WE APPRECIATE YOUR BUSINESS, CHECK THE FRONT DESK FOR EVENTS'
  ],
  stagger: 35,
  flipDuration: 120
});

board.play();

React Usage

import { Flipboard } from '@c-hinck10/splitflap-js/react';
import '@c-hinck10/splitflap-js/styles.css';

export function HeroBoard() {
  return (
    <Flipboard
      size="6x22"
      align="center"
      autoplay
      pageDuration={4000}
      trigger="visible"
      messages={[
        'WELCOME TO THE HILTON BEAVER CREEK, WE APPRECIATE YOUR BUSINESS, CHECK THE FRONT DESK FOR EVENTS'
      ]}
    />
  );
}

Core API

type TriggerMode = 'load' | 'visible' | 'manual';

type FlipboardOptions = {
  size?: '3x15' | '6x22';
  rows?: number;
  cols?: number;
  align?: 'left' | 'center' | 'right';
  preserveWords?: boolean;
  tone?: 'default' | 'muted' | 'accent' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple';
  theme?: 'classic' | 'menu' | 'playful';
  trigger?: TriggerMode;
  stagger?: number;
  staggerMode?: 'simultaneous' | 'row' | 'sequence';
  flipDuration?: number;
  charset?: string;
  faces?: FlipboardFace[];
  shadow?: boolean | string;
  tileShadow?: boolean | string;
  loop?: boolean;
  autoplay?: boolean;
  pageDuration?: number;
  paginate?: boolean;
  messages?: string[];
  pages?: FlipboardPage[];
  startIndex?: number;
  respectReducedMotion?: boolean;
  pauseWhenHidden?: boolean;
  responsive?: boolean;
  performanceMode?: 'auto' | 'off' | 'on';
  flipDirection?: 'forward' | 'shortest';
  onComplete?: (message: string, index: number) => void;
};

class Flipboard {
  constructor(container: HTMLElement, options?: FlipboardOptions);

  setMessage(message: string): void;
  setMessages(messages: string[]): void;
  play(index?: number): void;
  next(): void;
  reset(): void;
  destroy(): void;
}

preserveWords is enabled by default, so messages are wrapped per row when possible instead of being sliced through the middle of a word. For classic board composition, align: 'center' is also the default.

paginate is also enabled by default, so one long message can automatically become multiple board pages. Pair that with autoplay: true and pageDuration to rotate through those pages on a timer.

For smoother behavior on phones and lower-power devices, the board now defaults to:

  • respectReducedMotion: true so it renders immediately when the user prefers reduced motion
  • pauseWhenHidden: true so autoplay timers stop in background tabs and resume when visible again
  • responsive: true so compact board styling can kick in on smaller or coarse-pointer layouts
  • performanceMode: 'auto' so mobile-style layouts can use lighter animation timing automatically
  • flipDirection: 'forward' by default for classic split-flap behavior, with an option to use the shortest path instead

shadow controls the outer board drop shadow:

  • true keeps the package default
  • false disables it
  • a CSS box-shadow string overrides it, for example 0 12px 24px rgba(0, 0, 0, 0.18)

tileShadow controls the per-tile shadow:

  • true keeps the package default
  • false disables it
  • a CSS box-shadow string overrides it

If you want to override the shadow from your own page CSS, set --fb-board-shadow on the board container or a parent wrapper:

.hero-board-wrap {
  --fb-board-shadow: 0 0 0 rgba(0, 0, 0, 0);
  --fb-tile-shadow: none;
}

For flatter or more custom boards, you can also override the packaged gloss and fill layers directly:

.hero-board-wrap {
  --fb-board-background: #f7f7f5;
  --fb-board-inset-shadow: none;
  --fb-tile-background: var(--fb-tile-bg);
  --fb-tile-inset-shadow: none;
  --fb-tile-shadow: none;
  --fb-flap-background: var(--fb-tile-background);
}

Face Wheel

The simple API still works with charset, but the animation model now treats a flap face as a full visual state, not just a character.

That means a face can include:

  • char
  • tone
  • optional id
  • optional label

For most boards, you can keep using charset and let the package build a default face wheel for you. That default wheel preserves normal character flips and includes blank-tone decor faces for colored spacer rows.

If you want explicit control over flap ordering, pass faces:

import { Flipboard, createDefaultFaces } from '@c-hinck10/splitflap-js';

const faces = [
  ...createDefaultFaces(' ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
  { id: 'rainbow-start', char: '*', tone: 'accent' },
  { id: 'purple-blank', char: ' ', tone: 'purple' }
];

const board = new Flipboard(container, {
  size: '6x22',
  faces,
  flipDirection: 'shortest',
  pages: [
    {
      rows: [
        {
          kind: 'spacer',
          leadingDecor: [{ char: ' ', tone: 'purple' }]
        }
      ]
    }
  ]
});

Order in faces defines rotation order. That gives you a path to authentic boards where decorative symbols, letters, blanks, and tone variants all exist as real flap faces.

flipDirection controls how the board walks that wheel:

  • forward always advances in wheel order
  • shortest chooses the shortest available path, forward or backward

If both directions are tied, shortest falls back to forward so behavior stays deterministic.

Size Presets

Two board presets are built in:

  • 3x15
  • 6x22

You can use size directly, or still pass custom rows and cols when you want a non-preset board.

Advanced Pages

For signage-style compositions, use pages instead of plain messages.

const pages = [
  {
    theme: 'playful',
    rows: [
      {
        kind: 'spacer',
        leadingDecor: ['purple', 'blue', 'green', 'yellow', 'orange', 'red'],
        trailingDecor: ['red', 'orange', 'yellow', 'green', 'blue', 'purple']
      },
      {
        text: 'WELCOME TO',
        align: 'center',
        leadingDecor: ['purple', 'blue', 'green', 'yellow'],
        trailingDecor: ['yellow', 'green', 'blue', 'purple']
      },
      {
        kind: 'columns',
        left: 'LATTE',
        right: '5.00/6.00',
        leftTone: 'default',
        rightTone: 'accent'
      }
    ]
  }
];

const board = new Flipboard(container, {
  size: '6x22',
  pages,
  autoplay: true,
  pageDuration: 4000
});

That advanced layer is what supports:

  • row offsets
  • left/center/right alignment
  • decorative edge tiles
  • menu-style left/right columns
  • theme presets and per-cell color tones

Styling

Import @c-hinck10/splitflap-js/styles.css, then override the board variables on the container or a parent element:

.hero-board {
  --fb-bg: #101010;
  --fb-tile-bg: #191919;
  --fb-text: #f5f2e8;
  --fb-gap: 8px;
  --fb-radius: 8px;
  --fb-font-size: 28px;
  --fb-font-weight: 500;
  --fb-letter-spacing: 0.03em;
  --fb-tile-aspect: 1 / 1;
  --fb-board-shadow: 0 12px 24px rgba(0, 0, 0, 0.18);
  --fb-board-inset-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --fb-tile-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  --fb-tile-inset-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), inset 0 -1px 0 rgba(0, 0, 0, 0.18);
  --fb-board-background: #111;
  --fb-tile-background: var(--fb-tile-bg);
  --fb-flap-background: var(--fb-tile-background);
}

When responsive is enabled, the board adds data-compact="true" on smaller/coarse-pointer layouts and adjusts spacing, radius, and font sizing. When performanceMode is active, it also adds data-performance="true" and uses lighter timing defaults.

Useful visual-tuning variables:

  • --fb-font-family
  • --fb-font-size
  • --fb-font-weight
  • --fb-letter-spacing
  • --fb-tile-aspect
  • --fb-radius
  • --fb-gap
  • --fb-board-inset-shadow
  • --fb-board-shadow
  • --fb-tile-shadow
  • --fb-tile-inset-shadow
  • --fb-board-background
  • --fb-tile-background
  • --fb-flap-background