Dot Matrix
import { useEffect, useMemo, useRef, useState } from 'react';
const css = `
.btn-dot-matrix {
display: inline-block;
padding: 12px 14px;
border: 1px solid light-dark(#dbdde1, #2e3035);
border-radius: 12px;
background: light-dark(#fbfcfd, #121417);
cursor: pointer;
box-shadow: inset 0 2px 10px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.4));
transition: border-color 0.3s ease;
}
.btn-dot-matrix:hover { border-color: light-dark(#6d7179, #7d8087); }
.btn-dot-matrix:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 3px; }
.btn-dot-matrix__grid {
display: grid;
grid-template-columns: repeat(var(--cols), 4px);
grid-auto-rows: 4px;
gap: 2px;
}
.btn-dot-matrix__dot {
border-radius: 50%;
background: light-dark(#ebedf0, #232529);
}
.btn-dot-matrix__dot[data-on] {
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 4px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 70%, transparent);
}
`;
// 5x7 glyphs, one string of five bits per row.
const FONT: Record<string, string[]> = {
A: ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
C: ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],
H: ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],
L: ['10000', '10000', '10000', '10000', '10000', '10000', '11111'],
N: ['10001', '11001', '10101', '10011', '10001', '10001', '10001'],
U: ['10001', '10001', '10001', '10001', '10001', '10001', '01110'],
'>': ['01000', '00100', '00010', '00001', '00010', '00100', '01000'],
' ': ['000', '000', '000', '000', '000', '000', '000'],
};
/** A message as columns of 7 bits, one blank column between glyphs. */
function columns(text: string): boolean[][] {
const cols: boolean[][] = [];
for (const ch of text) {
const g = FONT[ch];
for (let c = 0; c < g[0].length; c++) cols.push(g.map((row) => row[c] === '1'));
cols.push(Array(7).fill(false));
}
return cols;
}
const VISIBLE = 37;
const WORD = columns('LAUNCH');
const LOOP = columns('LAUNCH >> ');
const PAD = Math.floor((VISIBLE - WORD.length + 1) / 2);
export function DotMatrixButton() {
const [offset, setOffset] = useState<number | null>(null);
const timer = useRef(0);
useEffect(() => () => clearInterval(timer.current), []);
const lit = useMemo(() => {
const grid: boolean[] = [];
// At rest: the word, centred. Scrolling: the looped message from `offset`.
for (let r = 0; r < 7; r++)
for (let c = 0; c < VISIBLE; c++) {
grid.push(offset === null ? (WORD[c - PAD]?.[r] ?? false) : LOOP[(c + offset) % LOOP.length][r]);
}
return grid;
}, [offset]);
const start = () => {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
clearInterval(timer.current);
// Start where the resting word already sits, so the first frame doesn't jump.
let o = (LOOP.length - PAD) % LOOP.length;
setOffset(o);
timer.current = window.setInterval(() => setOffset((o = (o + 1) % LOOP.length)), 70);
};
const stop = () => {
clearInterval(timer.current);
setOffset(null);
};
return (
<>
<style>
{css}
</style>
<button
type="button"
className="btn-dot-matrix"
aria-label="Launch"
onPointerEnter={start}
onPointerLeave={stop}
onFocus={start}
onBlur={stop}
>
<span className="btn-dot-matrix__grid" style={{ '--cols': VISIBLE } as React.CSSProperties} aria-hidden="true">
{lit.map((on, i) => (
<span key={i} className="btn-dot-matrix__dot" data-on={on || undefined} />
))}
</span>
</button>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initBtnDotMatrix(el) on the mounted element instead.
<button type="button" class="btn-dot-matrix" aria-label="Launch">
<span class="btn-dot-matrix__grid" style="--cols:37" aria-hidden="true">
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot"></span>
<span class="btn-dot-matrix__dot" data-on="true"></span>
<span class="btn-dot-matrix__dot"></span>
</span>
</button>.btn-dot-matrix {
display: inline-block;
padding: 12px 14px;
border: 1px solid light-dark(#dbdde1, #2e3035);
border-radius: 12px;
background: light-dark(#fbfcfd, #121417);
cursor: pointer;
box-shadow: inset 0 2px 10px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.4));
transition: border-color 0.3s ease;
}
.btn-dot-matrix:hover { border-color: light-dark(#6d7179, #7d8087); }
.btn-dot-matrix:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 3px; }
.btn-dot-matrix__grid {
display: grid;
grid-template-columns: repeat(var(--cols), 4px);
grid-auto-rows: 4px;
gap: 2px;
}
.btn-dot-matrix__dot {
border-radius: 50%;
background: light-dark(#ebedf0, #232529);
}
.btn-dot-matrix__dot[data-on] {
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 4px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 70%, transparent);
}function initBtnDotMatrix(root) {
var dots = root.querySelectorAll('.btn-dot-matrix__dot');
if (!dots.length) return;
// 5x7 glyphs, one string of five bits per row.
var FONT = {
A: ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
C: ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],
H: ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],
L: ['10000', '10000', '10000', '10000', '10000', '10000', '11111'],
N: ['10001', '11001', '10101', '10011', '10001', '10001', '10001'],
U: ['10001', '10001', '10001', '10001', '10001', '10001', '01110'],
'>': ['01000', '00100', '00010', '00001', '00010', '00100', '01000'],
' ': ['000', '000', '000', '000', '000', '000', '000'],
};
function columns(text) {
var cols = [];
text.split('').forEach(function (ch) {
var g = FONT[ch];
for (var c = 0; c < g[0].length; c++) cols.push(g.map(function (row) { return row[c] === '1'; }));
cols.push([false, false, false, false, false, false, false]);
});
return cols;
}
var VISIBLE = 37;
var WORD = columns('LAUNCH');
var LOOP = columns('LAUNCH >> ');
var PAD = Math.floor((VISIBLE - WORD.length + 1) / 2);
var timer = 0;
// At rest: the word, centred. Scrolling: the looped message from `offset`.
function paint(offset) {
for (var r = 0; r < 7; r++) {
for (var c = 0; c < VISIBLE; c++) {
var on = offset === null ? !!(WORD[c - PAD] && WORD[c - PAD][r]) : LOOP[(c + offset) % LOOP.length][r];
dots[r * VISIBLE + c].toggleAttribute('data-on', on);
}
}
}
function start() {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
clearInterval(timer);
// Start where the resting word already sits, so the first frame doesn't jump.
var o = (LOOP.length - PAD) % LOOP.length;
paint(o);
timer = setInterval(function () { paint((o = (o + 1) % LOOP.length)); }, 70);
}
function stop() {
clearInterval(timer);
paint(null);
}
root.addEventListener('pointerenter', start);
root.addEventListener('pointerleave', stop);
root.addEventListener('focus', start);
root.addEventListener('blur', stop);
return function () {
clearInterval(timer);
root.removeEventListener('pointerenter', start);
root.removeEventListener('pointerleave', stop);
root.removeEventListener('focus', start);
root.removeEventListener('blur', stop);
};
}
document.querySelectorAll('.btn-dot-matrix').forEach(initBtnDotMatrix);Goal: Create a "Dot Matrix" button.
Visual behavior: An LED dot-matrix panel spells the label in lit dots. On hover or focus it scrolls the message like a station marquee, starting exactly where the resting word sits, and settles back when you leave.
Technique:
- The panel is a CSS grid of 37 x 7 round dots (grid-template-columns from a --cols variable); a lit dot gets the ink colour and a small glow.
- Letters come from a 5x7 bitmap font, turned into columns of seven booleans with a blank column between glyphs.
- At rest the word is centred; scrolling reads the looped message "LAUNCH >> " from an offset that advances every 70ms.
- The first scroll offset is computed so frame one matches the centred word, so nothing jumps when scrolling starts.
Accessibility: A real <button> named by aria-label="Launch"; the grid is aria-hidden. Focus scrolls it like hover. Under prefers-reduced-motion it never scrolls.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Dot Matrix" button.
Visual behavior: An LED dot-matrix panel spells the label in lit dots. On hover or focus it scrolls the message like a station marquee, starting exactly where the resting word sits, and settles back when you leave.
Technique:
- The panel is a CSS grid of 37 x 7 round dots (grid-template-columns from a --cols variable); a lit dot gets the ink colour and a small glow.
- Letters come from a 5x7 bitmap font, turned into columns of seven booleans with a blank column between glyphs.
- At rest the word is centred; scrolling reads the looped message "LAUNCH >> " from an offset that advances every 70ms.
- The first scroll offset is computed so frame one matches the centred word, so nothing jumps when scrolling starts.
Accessibility: A real <button> named by aria-label="Launch"; the grid is aria-hidden. Focus scrolls it like hover. Under prefers-reduced-motion it never scrolls.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
An LED dot-matrix panel spells the label in lit dots. On hover or focus it scrolls the message like a station marquee, starting exactly where the resting word sits, and settles back when you leave.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- hover, glow, continuous
- Techniques
- interval, css variables, state machine
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A real <button> named by aria-label="Launch"; the grid is aria-hidden. Focus scrolls it like hover. Under prefers-reduced-motion it never scrolls.