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

@enavermate/splitflap

v0.5.0

Published

Split-flap departure-board text for the web: every letter rolls, flips or flickers into place. A <split-flap> element for HTML, Vue, Svelte and Angular, a React component and a framework-free player

Downloads

43

Readme

splitflap

Split-flap departure-board text for the web — every letter rolls, flips or flickers into place. A <split-flap> element for HTML, Vue, Svelte and Angular, a React component and a framework-free player

npm bundle size license

Live demo

✅ Works with

| Framework | Checked with | How | | ---------------- | ----------------------- | --------------------------------------- | | ✅ HTML | any page, no build step | <split-flap> | | ✅ React | 18 and 19 | <Splitflap> | | ✅ Next.js | 16, App Router | <Splitflap>, plain text on the server | | ✅ Vue | 3.5 | <split-flap> | | ✅ Svelte | 5 | <split-flap> | | ✅ Angular | 22 | <split-flap> | | ✅ Anything else | — | SplitflapBoard on any element |

| ✅ Chrome | ✅ Firefox | ✅ Safari | ✅ Safari on iPhone | | :-------: | :--------: | :-------: | :-----------------: |

import { Splitflap } from '@enavermate/splitflap/react';

<Splitflap text={gate} transition="flip" />;
<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@enavermate/splitflap@0/dist/element.js"
></script>
<split-flap text="GATE A3" transition="flip"></split-flap>

✨ Features

  • 🎰 Four transitions — flip, reel, roll and scramble, for words and for numbers
  • 🔤 Any text, any script — Latin, Cyrillic, Greek, Armenian, Georgian, the Indic and Southeast Asian scripts, the kana and many numeral systems roll through their own alphabets; any other character still changes, in one step. Flags, emoji and Indic syllables stay whole in one cell
  • 📏 Cells that keep still — tabular digits by default, and two cell widths: text-like natural and uniform, a station board's fixed cells
  • 🎨 Styled like text — font, size, weight, colour and letter spacing come from your CSS
  • 🔁 Interruptible — a new text mid-flight continues from where every cell is, without a jump
  • ⏳ Loading built in — random words spin while data loads, then the real text lands
  • ♿ Accessible — the text stays in the DOM for screen readers, search and copying; the canvas is hidden from them, and prefers-reduced-motion shows changes at once
  • 🖥️ Server rendering — renders as plain text on the server and in jsdom, then animates once mounted: the Next.js App Router and an app's tests need nothing extra
  • 🧩 Every framework — React, a custom element for Vue, Svelte, Angular and plain HTML, and the player itself for anything else. No dependencies

📦 Installation

npm install @enavermate/splitflap

| Import | What it gives you | | ------------------------------- | --------------------------------------------- | | @enavermate/splitflap/react | <Splitflap> for React 18 and 19 | | @enavermate/splitflap/element | defines <split-flap> | | @enavermate/splitflap | SplitflapBoard (the player) and the planner | | @enavermate/splitflap/planner | the planner alone, no DOM |

Current Chrome, Firefox and Safari, on desktop and on iPhone: every release plays its demo in Chrome, Firefox and Safari's engine at desktop and iPhone sizes.

🚀 Quick start

React

import { Splitflap } from '@enavermate/splitflap/react';

function Gate({ status }: { status: string }) {
  return (
    <Splitflap
      text={status}
      transition="flip"
      cellWidth="uniform"
      style={{ font: '700 32px ui-monospace, monospace', color: '#f2c230' }}
    />
  );
}

A new text turns in from the one on screen. The component is a client component ('use client') and renders a <span>: pass className, style, id and other span attributes as usual.

HTML — no build step: the element straight from a CDN

<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@enavermate/splitflap@0/dist/element.js"
></script>

<split-flap class="gate" transition="flip">GATE A3</split-flap>

With a bundler, import '@enavermate/splitflap/element' once anywhere does the same.

Change the text attribute (or the text property) and the board turns to it. Without a text attribute the element's own text is the board's.

Vue — tell the compiler split-flap is a custom element:

// vite.config.js
vue({
  template: {
    compilerOptions: { isCustomElement: (tag) => tag === 'split-flap' },
  },
});
<script setup>
import { ref } from 'vue';
import '@enavermate/splitflap/element';
const gate = ref('GATE A3');
</script>

<template><split-flap :text="gate" transition="flip"></split-flap></template>

Svelte

<script>
  import '@enavermate/splitflap/element';
  let gate = $state('GATE A3');
</script>

<split-flap text={gate} transition="flip"></split-flap>

Angular — allow custom elements in the component:

import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
import '@enavermate/splitflap/element';

@Component({
  selector: 'app-gate',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<split-flap [attr.text]="gate()" transition="flip"></split-flap>`,
})
export class Gate {
  gate = signal('GATE A3');
}

Anything else

import { SplitflapBoard } from '@enavermate/splitflap';

const board = new SplitflapBoard(document.getElementById('gate')!, {
  text: 'MADRID',
});
board.update({ text: 'LISBOA', transition: 'roll' });

🎞️ Transitions

| transition | What every changed letter does | | ------------------ | ------------------------------------------------------------------------------------------------- | | reel (default) | Spins through the alphabet between the old letter and the new one, like a slot machine reel | | roll | Rolls through a few letters: its neighbours when the new one is close, random ones when it is far | | flip | Flips over in halves, like a departure board | | scramble | Flickers through random letters of its own script, then locks |

flip paints its flaps solid so the half they cover never shows through. The board takes their colour from the first opaque background around it; pass surfaceColor when the background is an image or a gradient. reel and roll take a direction: random (default — every other cell from above), auto (each cell the way its letter lies in the alphabet), up, down.

📏 Cell width

| cellWidth | Behaviour | | ----------- | ------------------------------------------------------------------------------------------------- | | natural | A cell moves from the old glyph's width to the new one's, as text reflows. reel and roll only | | uniform | Every cell as wide as the alphabet's widest letter, each glyph centred — a station board |

Without one, reel and roll reflow like text, and flip and scramble keep every letter close to its own width — narrow, regular and wide cells measured from the text's alphabets — since they show two glyphs in one cell at once and must not reflow.

⚙️ API

The React props, the element's attributes and the player's options are the same settings.

| Option | Attribute | Default | What it sets | | -------------------------------------- | --------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------- | | text | text | — | The text; the only one a board needs | | transition | transition | reel | reel, roll, flip, scramble | | surfaceColor | surface-color | from CSS | The colour under the text, for flip's flaps | | cellWidth | cell-width | per transition | natural (reel, roll), uniform | | direction | direction | random | reel and roll: random, auto, up, down | | duration, stagger | duration, stagger | 650, 40 | Ms per cell (flip takes 1.6×), ms between consecutive changed cells | | staggerOrder | stagger-order | ltr | ltr, rtl, random | | alphabet | alphabet | auto | auto, a script (greek), or { letters: '0123456789ABCDEF' } (attribute: the letters) | | loading | loading | false | Random words until it turns false; the real text then lands | | loadingLength | loading-length | text ±1 | 6 or { min: 4, max: 10 } (attribute: 4-10) | | seed | seed | 1 | The same seed plays the same way | | contentKey | content-key | — | Replans when it changes even if the text did not: a reused list row | | animateOnMount | animate-on-mount | true | false shows the first text, and the first after contentKey changes, at once | | reduceMotion | reduce-motion | system | system follows prefers-reduced-motion; always, never | | tabularDigits | tabular-digits | on | Digits of one width, so numbers keep still; off with font-variant-numeric: proportional-nums too | | onTransitionStart, onTransitionEnd | splitflap-transition-start, splitflap-transition-end events | — | { text } and { text, interrupted } (an event's detail) |

The font, size, weight, colour, line height and letter spacing are the element's CSS. After a change the board cannot observe (a class toggled on an ancestor), call refresh() on the element or the player; font loading is picked up by itself. SplitflapBoard also has update(options) and destroy(), which puts the plain text back.

The planner — createBoard(), planTransition(board, input) and loadingPlan(length, seed, options) return every cell's path, timing and direction as plain data (@enavermate/splitflap/planner, no DOM), so a board's behaviour can be tested without rendering, or drawn some other way.

🔤 Scripts

A cell rolls through the alphabet of its own letter's script, read from the text — no language setting. A word travels only through the core letters and the extra letters it contains, so a Ukrainian word never flashes a letter only Belarusian uses. Chinese, Korean and anything else without an alphabet changes in one step: no script can break a board. Right-to-left scripts are not supported.

🧪 Tests and server rendering

On a server the React component renders <span>{text}</span>; <split-flap> with its text inside is plain HTML too. In jsdom and happy-dom, which have no canvas, the board stays plain text and reports every change as finished, so the tests of an app that uses it run unchanged.

🌍 Also for React Native and Swift

The same board, plan for plan: react-native-splitflap for React Native and Expo (npx expo install @enavermate/react-native-splitflap), and SplitflapKit for SwiftUI and UIKit. All three share one planner, checked against the same 131 golden scenarios, so a text animates the same way everywhere. How it is built: scivi.dev/oss/react-native-splitflap.

🤝 Contributing

Found a bug, or missing something? Open an issue. See CONTRIBUTING; everyone taking part follows the code of conduct.

📄 License

MIT © Roman Diukachov