solari-split-flap
v1.0.0
Published
A physically accurate split-flap (Solari board) display built with vanilla HTML, CSS, and JavaScript. No dependencies.
Downloads
109
Maintainers
Readme
Solari Split-Flap Display
A physically accurate split-flap (Solari board) display built with vanilla HTML, CSS, and JavaScript. No dependencies.

Features
- Physically correct drum mechanics: each cell has a single drum that only spins forward, cycling through every intermediate character in sequence (space → A–Z → 0–9 → punctuation), just like real Solari hardware
- Deceleration: flaps spin fast at the start and slow down as they settle on the target character
- Synthesized click sound: Web Audio API generates a short filtered-noise burst per flip (no audio files needed). Activates on first user interaction
- Author names in yellow: lines prefixed with
@render in gold, right-aligned - Cascading stagger: cells flip left-to-right, top-to-bottom with a 50ms delay per cell
- 10 color themes: press
Tto cycle through dark and light hue variations (classic, mint, ocean, purple, amber, rose, fog, sage, lavender) - HSL theme engine:
generateTheme(hue, sat, mode)calculates physically correct flap colors: top flap lighter (catches light), bottom flap darker (in shadow), board recessed behind both - Responsive: scales down on tablet and mobile
- Zero dependencies: single HTML file, no build step
Install
npm install solari-split-flapUsage
Vanilla JS
import { SolariBoard } from 'solari-split-flap';
const board = new SolariBoard(document.getElementById('board'), {
cols: 20,
rows: 8,
theme: 'ocean',
quotes: [
['HELLO WORLD', '', '@SOLARI'],
['STAY HUNGRY.', 'STAY FOOLISH.', '', '@STEWART BRAND'],
],
});
board.start();React
import { Solari } from 'solari-split-flap/react';
function App() {
return (
<Solari
cols={20}
rows={8}
theme="mint"
quotes={[
['HELLO WORLD', '', '@SOLARI'],
['STAY HUNGRY.', 'STAY FOOLISH.', '', '@STEWART BRAND'],
]}
/>
);
}Props: cols, rows, theme, quotes, sound, flipMs, charDelay, holdMs, autoStart, className, style, onReady (receives the board instance).
CDN
<div id="board"></div>
<script src="https://unpkg.com/solari-split-flap"></script>
<script>
const board = new SolariBoard(document.getElementById('board'), {
theme: 'purple'
});
board.start();
</script>Static HTML
Open index.html in a browser. That's it.
Customization
Edit the quotes array in the <script> block. Each quote is an array of lines:
var quotes = [
['FIRST LINE',
'SECOND LINE',
'',
'@AUTHOR NAME'], // @ prefix = yellow, right-aligned
['ANOTHER QUOTE',
'',
'@SOMEONE'],
];Configuration variables
| Variable | Default | Description |
|----------|---------|-------------|
| COLS | 20 | Number of columns (characters per row) |
| ROWS | 8 | Number of rows |
| FLIP_MS | 150 | Single flap animation duration (ms) |
| CHAR_DELAY | 50 | Stagger between cells in cascade (ms) |
| HOLD_MS | 5000 | Time to hold a finished quote before cycling (ms) |
Themes
Press T to cycle through the built-in themes. You can also create your own:
// generateTheme(hue, saturation, mode)
// hue: 0-360, sat: 0-100, mode: 'dark' or 'light'
var theme = generateTheme(155, 30, 'dark'); // mint dark
applyTheme(theme);The theme engine uses HSL to calculate physically correct values:
| Surface | Dark mode | Light mode | Why | |---------|-----------|------------|-----| | Body bg | L: 6% | L: 95% | Deepest layer | | Board bg | L: 10% | L: 88% | Recessed behind flaps | | Top flap | L: 18% | L: 82% | Catches light from above | | Bottom flap | L: 14% | L: 76% | In shadow below split | | Gap line | L: 6% | L: 70% | Shadow between flap halves |
All colors use CSS custom properties (--sf-*), so you can also override them directly in CSS.
For achromatic themes (saturation 0), author names stay yellow. For chromatic themes, author names use the theme's own hue at high saturation for a cohesive feel.

Embedding
Drop the HTML into any page. The display is a self-contained <div> with scoped styles and an IIFE script, no global pollution.
How it works
Each cell contains four layers:
- Top half: static, shows the current character's upper portion
- Bottom half: static, shows the next character's lower portion
- Flip front: animated flap showing the old character (flips down)
- Flip back: backside of the flap showing the new character (revealed as flap lands)
The flip uses CSS rotateX(-180deg) with transform-origin: bottom center and backface-visibility: hidden for a realistic 3D fold.
The drum sequence is fixed: ' ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,:;!?\'-'. To go from any character to any other, the drum advances forward through every intermediate position, wrapping around if needed. This matches how mechanical split-flap displays physically operate.
License
MIT
