HowlBox is a self-hosted, client-only Twitch chat overlay for OBS browser sources. It joins Twitch chat anonymously, renders native Twitch, 7TV, BTTV, and FrankerFaceZ emotes with badge art, and takes its entire configuration from URL query parameters, so an OBS source URL is the whole setup.
Reading chat anonymously is what removes the account, and it is also
the limit: HowlBox cannot send messages, moderate, or show follower
alerts (follows only arrive over EventSub). Subs, gifts, raids, and
cheers do arrive over anonymous IRC, and the events parameter
renders them. If you need to send, moderate, or see follows, you need
an overlay with a backend.
Your chat. Your colors. Your howl.
- Anonymous chat connection - Read-only Twitch chat over
@twurple/chat. No account, no keys, nothing to expire mid-stream. - 31 themes in four families - Clean (wolf glass, Liquid Glass,
midnight, daylight, high contrast, bare text, gold luxe, brutalist,
holographic), Gamer (synthwave neon, Xbox, galaxy, ember, cyberpunk,
sci-fi HUD, aurora), Cozy (kawaii pastel, mocha, sakura, forest,
deep sea, frost, sticky note, comic), and Retro (CRT terminal,
Windows 95, Windows XP, pixel arcade, Game Boy, VHS, vaporwave).
Nine of them carry color variants through
variant, for 45 looks in total. All CSS-variable driven, and a browser source downloads only the CSS for the theme in its URL. - Three display modes - Transparent messages over gameplay
(
bg=off), one themed backdrop panel (bg=panel), or per-message bubbles (bg=bubble). - Full emote support - Native Twitch emotes plus 7TV (including zero-width overlay emotes), BTTV, and FrankerFaceZ, resolved per channel and cached in localStorage.
- Adjustable emote size -
emotescale(1 to 4, half steps) grows messages that are nothing but emotes, without inflating regular text messages. - Ticker mode -
scroll=tickerlays chat out as one horizontal lane sliding right to left, for a strip under the gameplay instead of a column beside it.scrollspeedruns it from 1x to 5x, with 1x a deliberate crawl. A lane fits about one message every eight seconds at 1x, so on a fast channel it shows a sample of recent chat rather than all of it. - Badge art without secrets - Every global and per-channel Twitch
badge (including subscriber and bits art) via public, CORS-safe
APIs, plus custom badge art overrides inline through the
badgeartparameter or hosted in a public GitHub gist throughbadgegist. - Pronoun badges - Opt-in (
pronouns=true) pronoun badges next to names, from pronouns.alejo.io, the service 7TV and FrankerFaceZ read. Each chatter's login is looked up there; enable it only if that third-party call is acceptable for your channel. - Profile picture avatars - Opt-in (
avatars=alloravatars=subs) chatter avatars before names, fetched in batches from api.ivr.fi and cached locally.subsmode only looks up subscribers and founders. - Sub, cheer, raid, and first-chat alerts - Opt-in
(
events=sub,cheer,raid,first,announceorevents=all) event rows for subs, gift bombs, raids, cheers, first-time and returning chatters, and announcements, riding the same anonymous connection as chat. A 100-gift bomb collapses to one row instead of one hundred. - Moderation aware - Deleted messages, timeouts, and bans vanish from the overlay instantly. An optional delay holds non-mod messages so moderation lands before anything renders.
- Filters - Hide known bots, hide
!commands, hide specific users, or run featured mode showing only chosen users. - URL-only configuration - Every option is a query parameter. No config files, no dashboard, no stored state.
- Configurator page - Pick options live at
/configwith a real overlay preview, then copy a ready OBS source URL. The landing page at/shows a theme-switching demo. - OBS-optimized - Transparent from first paint, zero blur filters (safe on CPU-rendered setups), event-driven reconnects that survive hidden-source timer throttling, and a visible connection status pill.
- Stable styling hooks - Every element carries
hb-*class names as a contract for the OBS Custom CSS field.
-
Clone the repository:
git clone https://github.com/mrdemonwolf/howlbox.git cd howlbox -
Install dependencies:
bun install
-
Start the dev server:
bun run dev:web
-
Open
http://localhost:3001for the landing page, then head tohttp://localhost:3001/configto build a URL and add it as an OBS browser source.
Build an overlay URL with the builder at /config, or hand-write one:
/overlay?channel=mrdemonwolf&theme=wolf&bg=bubble&hidebots=true&fade=30
The full reference lives at /docs on the deployed site
(mrdemonwolf.github.io/howlbox/docs):
every parameter with its own anchor, both custom badge art formats,
the hb-* Custom CSS contract, and troubleshooting. The table below is
the short version.
| Parameter | Values | Description |
|---|---|---|
channel |
Twitch login name | Channel to join (required) |
theme |
31 values, listed in full at /docs#themes | Color theme preset (default wolf) |
variant |
theme-specific (e.g. theme=terminal&variant=amber) |
Color variation of the chosen theme; unknown values fall back to the theme default |
layout |
inline, stacked |
Name inline with the message, or on its own line above it |
align |
left, right |
Which edge messages hug; under scroll=ticker, which way the lane travels |
group |
true, false |
Consecutive messages from one chatter share a single header |
scroll |
off, ticker |
Stacked column, or one horizontal lane sliding right to left |
scrollspeed |
1 to 5, whole steps (default 1) |
Ticker lane speed; ignored unless scroll=ticker |
bg |
off, panel, bubble |
Display mode (default off). panel draws its backdrop only while there are messages |
size |
percent, 50 to 300 (default 100) |
Scales theme text, with a 12px rendered floor |
emotescale |
1 to 4, half steps (default 1) |
Grows emotes on messages that are nothing but emotes |
max |
1 to 200 (default 50) |
Max messages kept on screen |
hidebots |
flag | Hide known chat bots (Nightbot, etc.) |
hide |
comma-separated logins | Always hide these users |
allow |
comma-separated logins | Featured mode: only show these users |
hidecommands |
flag | Hide messages starting with ! |
delay |
seconds, 0 to 300 (default 0) |
Hold non-mod messages so deletions land before display |
badges |
false to disable (default on) |
Badge icons before names |
pronouns |
flag | Pronoun badge before names (via pronouns.alejo.io) |
avatars |
off, all, subs (default off) |
Profile pictures before names (via api.ivr.fi). subs only looks up subscribers and founders |
events |
comma-separated sub, cheer, raid, first, announce, or all |
Show sub, gift, cheer, raid, first-message and announcement rows (default none) |
timestamps |
flag | HH:MM before each message |
animate |
false to disable (default on) |
Slide/fade entrance animation |
media |
animated, static (default animated) |
Static art avoids continuous decode and repaint |
fade |
seconds, 0 to 600 (default 0) |
Auto-hide each message N seconds after it appears |
badgeart |
comma-separated set=url or set/version=url pairs |
Custom badge art overriding the Twitch defaults |
badgegist |
public gist id or URL | Custom badge art hosted in a gist (same pairs, one per line, or a JSON map) |
refresh |
minutes, 0 or 1 to 1440 (default 0) |
Re-fetch channel art every N minutes; 1-4 round up to 5; globals retain their TTL |
Invalid or missing values fall back to safe defaults; a typo in OBS never produces a blank overlay.
Events ride the same anonymous connection the chat does, so events
needs no account: subs, gifts, raids and cheers all arrive as ordinary
IRC traffic. Follows are the exception, since they only exist on
EventSub. A sub or raid renders as a whole sentence with no separate
name header; a cheer, first message or announcement decorates the
message it arrived with.
Custom badge art precedence, weakest to strongest: fetched Twitch art,
then badgegist, then inline badgeart. A bare set key (no
/version) covers every version of that set. The gist is fetched from
the public GitHub API (no token), which allows 60 unauthenticated
requests per hour per IP. Refresh is off by default; when enabled, a
value of 1 to 4 rounds up to the 5-minute floor rather than falling
back to off, which keeps a gist well under that limit.
The art URLs in badgeart and badgegist point at whatever image host
you name, and your browser fetches them directly. Only HTTPS URLs are
accepted (URLs carrying credentials are rejected), and badge, emote, and
avatar images load with a no-referrer policy so the overlay URL is not
sent to those hosts.
In OBS 31 or newer, add a Browser source with the generated URL and set Width and Height on the source itself (try 480 x 800). Leave "Shutdown source when not visible" and "Refresh browser when scene becomes active" off.
| Layer | Technology |
|---|---|
| Framework | React 19 + TanStack Router (Vite) |
| Language | TypeScript (strict) |
| Chat | @twurple/chat (anonymous IRC) |
| Emotes | 7TV, BTTV, FrankerFaceZ public APIs |
| Badges | api.ivr.fi (Twitch badge art) |
| Pronouns | pronouns.alejo.io (opt-in) |
| Avatars | api.ivr.fi (batched, opt-in) |
| Cheermotes | static-cdn.jtvnw.net (global tiers) |
| Styling | Tailwind CSS 4 + CSS variables |
| UI | shadcn/ui primitives (packages/ui) |
| Monorepo | Turborepo + Bun workspaces |
| Linting | Biome |
| Deploy | GitHub Pages (static) |
- Bun 1.3 or newer
- OBS Studio 31 or newer (for overlay testing)
-
Install dependencies:
bun install
-
Run the web dev server:
bun run dev:web
bun run dev: Start all apps in development modebun run dev:web: Start only the web app (port 3001)bun run build: Build the static site toapps/web/distbun run check-types: Check TypeScript types across the monorepobun run check: Run Biome formatting and linting
- TypeScript strict mode across every workspace
- Biome for linting and formatting
- Turborepo task caching for fast builds
hb-*class names on overlay elements are a public contract for OBS Custom CSS overrides; keep them stable
Pushes to main build and deploy automatically via
.github/workflows/deploy.yml. One-time setup: in the repository
settings, set Pages > Source to "GitHub Actions". The site serves at
https://mrdemonwolf.github.io/howlbox/ (the build sets
BASE_PATH=/howlbox/; use a custom domain and drop the variable for
root hosting).
The build generates a 404.html marked noindex plus a real index.html
per route (including /overlay), so every route resolves with an
HTTP 200 instead of falling back to the SPA's 404 status.
bun run build produces a fully static site in apps/web/dist. Serve
that folder as-is (set BASE_PATH at build time if hosting under a
subpath). No server runtime is required.
Once deployed, these are ready to paste into an OBS browser source (swap the channel for testing against a busier chat):
https://mrdemonwolf.github.io/howlbox/overlay?channel=mrdemonwolf&theme=wolf&bg=off&hidebots=true
https://mrdemonwolf.github.io/howlbox/overlay?channel=mrdemonwolf&theme=glass&bg=bubble&fade=30
https://mrdemonwolf.github.io/howlbox/overlay?channel=xqc&theme=terminal&bg=panel×tamps=true
Suggested source size: 480 x 800 at the default font scale.
howlbox/
├── apps/
│ └── web/
│ └── src/
│ ├── components/
│ │ ├── chat/ # Overlay renderer + theme CSS variables
│ │ └── landing/ # Landing demo + /config URL builder
│ ├── hooks/ # Chat connection, emote/badge loading
│ ├── lib/
│ │ ├── emotes/ # 7TV/BTTV/FFZ fetch, cache, resolution
│ │ ├── overlay/ # URL param schema + builder
│ │ └── twitch/ # Anonymous chat client, badges, colors
│ └── routes/ # / landing, /config builder, /docs reference, /overlay
├── packages/
│ ├── config/ # Shared tsconfig base
│ └── ui/ # Shared shadcn/ui components and styles
├── .github/workflows/ # CI deploy to GitHub Pages
├── biome.json # Lint and format config
└── turbo.json # Turborepo pipeline
Have questions or feedback?
- Discord: Join my server
Made with love by MrDemonWolf, Inc.