telop-ui
v0.5.0
Published
Japanese TV-broadcast style React UI kit for projectors, signage and live presentations — telops, lower thirds, big numbers, rankings and wipes.
Maintainers
Readme
telop-ui
Japanese TV-broadcast style UI for React — for the screen at the front of the room. Projectors, lobby signage, live quizzes and votes, ceremonies, countdowns.
- Telops and lower thirds: slanted caption boxes, speaker name cards, headline boxes
- Numbers that perform: count-up, slot-machine digit rollers, split-flap boards, countdowns
- Live charts: rankings whose rows slide to their new place, choice bars with a stamp on the answer, a drumroll reveal
- Broadcast effects: wipes, sweep banners, slide-in tickers, credits rolls, spotlight, eyecatch curtain between slides
- Made for projection: sizes scale with
min(vw, vh), two colors plus ink, a dark mode for dim rooms, controls that hide until the mouse moves
No CSS framework needed. React 17+, TypeScript types included, no runtime dependencies.
Install
npm install telop-uiQuick start
import { TelopRoot, Stage, StageHeader, LBand, LBandItem, Telop, BigNumber, SegBar } from "telop-ui";
export function Submissions({ done, total }: { done: number; total: number }) {
return (
<TelopRoot locale="en">
<Stage
header={<StageHeader logo="/logo.svg" title="Live Quiz" subtitle="Room A" />}
band={<LBand tag="LIVE"><LBandItem>Room A</LBandItem></LBand>}
>
<Telop className="tu-wipe">Submitted</Telop>
<BigNumber value={done} size="hero" tone="primary" />
<SegBar total={total} filled={done} tall />
</Stage>
</TelopRoot>
);
}TelopRoot injects the stylesheet once. If you prefer a static file, pass
injectStyles={false} and import "telop-ui/styles.css".
Themes
Three presets: broadcast (navy + red), variety (ink + orange) and ceremony (green + gold).
For your own, two brand colors are enough:
import { createTheme, TelopRoot } from "telop-ui";
const school = createTheme("school", { primary: "#1d6b46", accent: "#b07f00" });
<TelopRoot theme={school} mode="dark">…</TelopRoot>createTheme derives everything else for both modes:
- tinted background, panel, text and muted text from
primary - text on each fill — white while it reads (3:1 for bold captions), dark otherwise
- dark-mode versions of your colors, lifted until they read on the dark background
Any field can still be set by hand (bg, fg, panel, onPrimary, onAccent, mute, track),
and a third argument overrides dark-mode fields.
Contrast check. In development, a theme that would be hard to read on a projector is reported once in
the console. checkTheme(theme) returns the same list, and contrastRatio, readableOn, mix and
ensureContrast are exported if you build colors yourself. Try colors in the demo
(Theme → Custom): it shows the warnings and the createTheme call, and the link keeps your colors.
Every color is also a CSS variable (--tu-bg, --tu-fg, --tu-primary, --tu-accent, --tu-on-accent, --tu-panel,
--tu-mute, --tu-track …), so you can override them in plain CSS.
Use accent sparingly. It is for the one thing people must look at: the hardest question, the winner, the
last ten seconds. If everything is red, nothing is.
Global style: shape · motion · density
Three switches on TelopRoot change every part at once:
<TelopRoot shape="round" motion="calm" density="compact">…</TelopRoot>| Prop | Values | What changes |
|---|---|---|
| shape | slant (default) · square · round | Every box: slanted TV captions, upright blocks, or rounded corners |
| motion | calm · normal (default) · snappy | Every animation (×1.6 / ×1 / ×0.6). Timers that wait for an animation scale with it |
| density | comfortable (default) · compact | Spacing in lists and panels |
They are CSS variables underneath (--tu-skew, --tu-radius, --tu-speed, --tu-density), so you can also fine-tune them in CSS.
On a web page
TelopRoot is built for a full-screen show: it fills the viewport, hides overflow and blocks text
selection, and every size follows min(vw, vh). Inside an ordinary scrolling page, pass
surface="page":
<TelopRoot surface="page" loadFont={false}>
<Telop size="xl" wrap>Autumn Festival 2026 — Program and Timetable</Telop>
<LowerThird label="Venue">Main Hall, 2F — doors open 13:30</LowerThird>
</TelopRoot>- Natural height, nothing clipped, text can be selected and copied, the page keeps its own background and font.
- Sizes switch to rem-based tables (
PAGE_TEXT·PAGE_TELOP·PAGE_NUMBER), so text stays readable on a portrait phone. Size names (sm,xl…) mean the same thing on both surfaces. - The top band and body paint are off unless you pass
band/paintBody. <Telop wrap>lets a long caption break onto more lines (a TV telop is one line by default).
Entrances on a long page. An animation that runs on mount has finished before anyone scrolls
to it, so give the entrance on="view":
<Telop animate="sweep" on="view">Program</Telop> {/* a bar runs across, then the caption */}
<Reveal on="view" delay={200}><Text>…</Text></Reveal> {/* wipes in when it scrolls into view */}
<Stagger on="view"><p>…</p><p>…</p></Stagger> {/* one after another */}useInView() gives the same trigger to your own parts. Without IntersectionObserver everything shows at
once, and prefers-reduced-motion skips the movement.
The page surface is for the small parts — Telop, Text, LowerThird, Tag, Pill, BigNumber.
Full-screen layouts (Stage, SlideDeck, the TV segments) still assume a screen.
Live data
import { usePolling, useNow, StatusScreen, Countdown } from "telop-ui";
const { data, error, stale, skew, refresh } = usePolling(() => fetch("/api/live").then((r) => r.json()), {
interval: 4000,
serverTimeOf: (d) => d.serverTime, // correct the projector's clock
});
const now = useNow(1000, skew);
if (!data) return <StatusScreen message={error} onRetry={refresh} />;When a refresh fails after data is on screen, the old data stays and stale is set — a projector
should never go blank in front of an audience.
Components
| Group | Components |
|---|---|
| Root & hooks | TelopRoot useTelop useMessages useSizes · usePolling useNow useCountUp useInView useAutoHide useHotkeys useStoredMode · formatClock formatCountdown toggleFullscreen |
| Layout | Stage StageHeader ControlBar CtrlButton Split Panel CardGrid Card LBand LBandItem Marquee StatusScreen |
| Text | Telop Text Tag Pill LiveDot HeadlineBox LowerThird PersonLowerThird TitleCard BigMessage Typewriter |
| Numbers | BigNumber StatBlock SegBar ProgressBar Countdown Clock DigitRoller SplitFlap |
| Charts | RankingList RankingRow RankingRace BarList StackedBar ChoiceBars Stamp DrumrollReveal |
| Effects | TickerStack SweepBanner Reveal Stagger CreditsRoll Spotlight |
| TV segments | MekuriBoard JudgeScores VersusMeter ScoreBug RankReveal NewsFlash |
| Slides | SlideDeck (curtain / fade / slide transitions, auto-advance, ← → / Space) |
TV segments
Formats borrowed from specific kinds of Japanese TV. Each is driven by the parent — the presenter decides the moment.
| Component | What it is | Forms |
|---|---|---|
| MekuriBoard | Wide-show flip board: answers under paper strips, peeled one by one | peel: right · up · flip — columns |
| JudgeScores | Judges' panel: scores one by one, then the total counts up | reveal: flip · rise · count — columns, hideTotal |
| VersusMeter | Two teams on one meter (red vs white, sports day) | variant: bar · split |
| ScoreBug | Corner scoreboard of a sports broadcast | variant: stack · inline |
| RankReveal | Countdown ranking, announced from the bottom up | layout: list · podium |
| NewsFlash | Breaking-news bar from the screen edge | position: top · bottom — tone: flash · alert |
Most other parts have forms too — e.g. Telop variant="underline", ProgressBar variant="ring",
Countdown variant="ring", SegBar variant="dots", TitleCard align="center", SlideDeck transition="fade".
The demo lists every parameter with its default.
Utility classes: tu-wipe (wipe in), tu-wipe is-start / is-trail (stagger), tu-muted, tu-spacer.
Built-in strings come in ja (default), en and zh; override any of them with messages.
Demo
Online: https://tyoukyou23.github.io/telop-ui/ — open a scene and press F for full screen.
To run it locally:
git clone https://github.com/tyoukyou23/telop-ui && cd telop-ui
npm install
npm run devFive complete scenes (live quiz, live vote, departure board, ceremony slides, countdown) and every component with a code snippet. Switch language, theme and light/dark at the top. Open a scene and press F for full screen.
Design notes
- Everything in a list is on screen within 0.7 s. Staggered entrances look nice the first time and slow the tenth time.
- No glow, no gradients. Flat color blocks and slanted boxes read from the back of a room.
- Theme switches are instant. The background never transitions, or dark boxes float on a light page for half a second.
prefers-reduced-motionslows the endless loops (marquee, credits) and disables the big moves.- Don't rank people in front of an audience. The ranking components are for questions, options and teams.
License
MIT © tyoukyou23
