Songs are saved on this device first. Open the Local tab and press Upload online to send one to staff for review.

A theme is just a short list of colors. Rhythmhel reads them the moment you leave the box, saves them on this device, and repaints everything, including the playfield.
--accent:#ff66aa;. Wrapping everything in :root{ ... } is optional.#12101a. Names and rgb() work too, but transparency is ignored./* text between these */ is a comment and is ignored.| --bg | Main background behind everything |
| --accent | Highlights: selected items, buttons, header underline, lane rails |
| --seltext | Text drawn on top of the accent color. Keep it readable against --accent |
| --panel | Cards, song rows and buttons |
| --panel2 | Header bars, side panels and text boxes |
| --edge | Borders and divider lines |
| --text / --dim | Normal text / quieter secondary text |
| --lane | Playfield background |
| --line | The hit line |
| --lane-1 to --lane-4 | Note colors for 4K, left to right (2K uses lanes 2 and 3, 3K uses 1 to 3) |
| --lane-solo | Note color in 1K |
| --perfect / --great | Judgement colors for good hits |
| --miss / --bad | Miss color / mash and fake-note warning color |
| --bg-dim | 0 to 0.9, how dark song backgrounds are. The Background dim option overrides it if you set one |
| --trail | Optional. Mouse trail color. If you leave it out, the trail is built from --accent and automatically adjusted so it stays visible on your background |
| --logo | Optional. Swaps the Rhythmhel logo (main menu hexagon, header bar, help windows). Use url("https://...") or a data: URL. Default is rhythmhel.png |
| color-scheme | Set to light for a light theme so form controls match |
--panel a little lighter than --bg, and --panel2 a little darker.color-scheme:light;, a dark --text, a dark --line, and --seltext:#ffffff..css file with Load a .css theme file, and share the file with friends.:root{
/* main colors */
--bg:#0c0d1f; /* background */
--accent:#a8a8e8; /* highlights and buttons */
--seltext:#0c0d26; /* text on the accent color */
--panel:#15173a; /* cards and buttons */
--panel2:#0e1029; /* bars, side panels, text boxes */
--edge:#3b3f78; /* borders and dividers */
--text:#eff0ff; /* normal text */
--dim:#9b9fcf; /* quiet text */
/* gameplay */
--lane:#080913; /* playfield */
--line:#ffffff; /* hit line */
--lane-1:#a8a8e8; /* 4K notes, left to right */
--lane-2:#e6b4ff;
--lane-3:#8fdcff;
--lane-4:#fff0b8;
--lane-solo:#e6b4ff; /* 1K notes */
--perfect:#c8c8f8;
--great:#b8f0d0;
--miss:#d05a5a;
--bad:#ff6a86;
--bg-dim:.6; /* song background darkness, 0 to 0.9 */
/* --logo:url("rhythmhel.png"); optional: swap the logo image */
/* --trail:#a8a8e8; optional: override the automatic mouse trail color */
}
Pick a key count and difficulty. A chart with the same name is replaced and its leaderboard is cleared.
Only the uploader (or staff) can export a song's charts.

A mod is one JavaScript file. Rhythmhel runs it with a single variable called rh. Mods are saved on this device and can be switched off or removed in Options, Mods.
A mod runs with the same power as the game itself: it can read your settings and your login session. Only install mods from people you trust, and read the code first.
counter.js (example below).// counter.js: shows a Perfect counter while playing
const size = rh.option({id:'size', label:'Counter size', type:'number', def:22, min:12, max:60});
rh.on('frame', screen => {
if (screen !== 'play') return;
rh.tx('Perfects ' + rh.play.c[0], 24, 150, size(), rh.theme.perfect);
});
rh.on('judge', j => { if (j.rating === 'Miss') rh.toast('Missed on lane ' + (j.lane + 1)); });
rh.menu('Hello', () => rh.toast('Hello from my mod!'));
rh.css(':root{--accent:#ff66aa}'); rh.refreshTheme();
| frame | Every frame after the screen is drawn. Gets the screen name: menu, songs, charts, lb, play, results, mp, room, edit, msg. Draw on rh.g |
| judge | A note was judged: {rating:'Perfect'|'Great'|'Miss', lane, time, combo, score} |
| start | A chart started. Gets the live play state |
| finish | A chart ended. Gets the results |
| key | Any key press (a KeyboardEvent) |
| option | A mod setting changed: {mod, id, value} |
| rh.menu(label, fn) | Adds an item to the main menu |
| rh.option({id,label,type,def,...}) | Adds a setting under Options, Mods. type: number, bool, text or select (with options:[...]). Returns a function that gives the current value |
| rh.store.get(k, def) / set(k, v) | Saves your own data on this device |
| rh.css(text), rh.refreshTheme() | Adds CSS. Use refreshTheme after changing color variables |
| rh.toast(text) | Small message at the top right |
| rh.go(screen) | Jump to menu, songs or mp |
| rh.tx, rh.rr, rh.btn, rh.panel, rh.hexP, rh.logo | The game's own drawing helpers: tx(text,x,y,size,color,align,maxWidth), rr(x,y,w,h,radius,fill,stroke), btn(x,y,w,h,label,fn,highlight), logo(x,y,width) |
| rh.g, rh.W, rh.H | Canvas context and its size. The height is always 720 units, the width follows the window |
| rh.cfg, rh.theme | Settings and theme colors (accent, text, lanes...) |
| rh.screen, rh.song, rh.user, rh.t | Current screen, selected song, player name, seconds since load |
| rh.play | Live play state while playing: score, combo, max, c ([perfect, great, miss]), m (mashes), K, notes. Otherwise null |
rh.play.score and expect it to be accepted: the server checks every score.Change each key separately: shape, color and image. It applies to that key's notes and its key button. “Default” follows your global note shape and the theme's lane color.
OptionsTune Rhythmhel to your liking