Use it in your project
Korokoro is an API and a tray, each usable without the other: an API of plain functions (roll, read notation, work out odds, keep a history), and a tray you mount into any element, which also comes as a React component, a Vue component and a web component.
1. The API alone
import { checkNotation, distributionOf, roll, seededSource } from "@johnmorrisdotca/korokoro";
const read = checkNotation("4d6dl1"); // { ok: true, spec } or the part refused and why
if (read.ok) {
const thrown = roll(read.spec, seededSource("table-7"));
thrown.faces; // [6, 4, 2, 1]: every die, in the order thrown
thrown.kept; // [true, true, true, false]: the 1 was dropped
thrown.total; // 12
distributionOf(read.spec).probabilities; // the exact chance of every total from 3 to 18
}2. The tray, in plain HTML
<div id="dice"></div>
<script type="module">
import { mountRoller } from "@johnmorrisdotca/korokoro";
mountRoller(document.getElementById("dice"), {
spec: { count: 1, sides: 20, modifier: 5 },
onRoll: (roll) => console.log(roll.total),
});
</script>3. React
import { DiceRoller } from "@johnmorrisdotca/korokoro/react";
export function Table() {
return <DiceRoller wide notation="2d20kh1+5" onRoll={(roll) => save(roll)} />;
}The component takes the tray's options as props, notation as a shorter way to give the dice (the tray follows it when it changes), and any attribute for its <div>. The tray mounts in the browser after the first render, so server rendering draws an empty box and nothing needs a provider. In Next.js, use it from a client component ("use client").
4. Vue
<script setup>
import { DiceRoller } from "@johnmorrisdotca/korokoro/vue";
</script>
<template>
<DiceRoller notation="2d20kh1+5" wide @roll="(roll) => save(roll)" />
</template>The props are the tray's options, with notation as a shorter way to give the dice, and each roll is a roll event. The dice and locale are followed as they change; roll(), history(), setSpec() and setLocale() are there on a template ref. It renders an empty box on the server (Nuxt included) and mounts the tray in the browser. Vue 3.3 or later.
5. A web component
<korokoro-roller notation="2d20kh1+5" wide></korokoro-roller>
<script type="module">
import { defineRoller } from "@johnmorrisdotca/korokoro/element";
defineRoller();
document.addEventListener("korokoro-roll", (event) => console.log(event.detail.total));
</script>A custom element, for any page and any framework that renders HTML. Call defineRoller() once; each roll is a korokoro-roll event that bubbles, with the roll as its detail.
Or with no call at all: importing @johnmorrisdotca/korokoro/element/define registers the element by being imported, so one script tag is the whole of it, from a CDN or from your own bundle:
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/korokoro@1/dist/element-define.js"></script>
<korokoro-roller notation="2d20kh1+5"></korokoro-roller>| Attribute | What it does |
|---|---|
notation | The dice showing at first, and again whenever it changes |
lang | ja for Japanese, anything else English; the page's own language when left out |
wide | Tray and panels side by side on a wide screen |
size | small is the felt and the result alone, medium adds the choice of dice, large (or left out) is everything |
sound="off" | No sound and no mute button |
hold="off" | Dice are not held |
placeholder="off" | The opening dice are the user's own roll |
keyboard="off" | Space rolls only when the focus is inside the tray |
language-chooser | The tray's own choice of English or 日本語 |
storage="none", storage-key | Keep no history; or the key it is kept under |
animation-ms, share-base, query | As the options of the same names |
cloth | The felt's cloth: green (unless said), blue, red, black or wood, the family's five; changed in place, keeping the dice and the rolls |
one-pip | The colour of a d6's one pip: red (unless said) or black; changed in place |
What an attribute cannot carry (a theme, your own words, your own sound) goes on the element's options property. roll(), setSpec() and history are on the element. The tray is drawn in the element's own light DOM, so the page's --kk-… variables theme it as they do a mounted tray.
One die on its own
A single die with nothing round it: no felt, no total, no panels. For a page that wants a die to look at, or one to tap. There are two kinds, chosen by one option.
- A die that rolls (
rollable, the default) is a button. A tap, Enter or Space throws it; it tumbles, lands on the face the generator chose before anything moved, and says so to a screen reader. - A die that does not roll (
rollable: false) is a picture of one face, the one you give it, changed from code withshow().
import { mountDie } from "@johnmorrisdotca/korokoro";
const die = mountDie(document.getElementById("die"), { sides: 20, size: "large", onRoll: (face) => console.log(face) });
die.roll(); // as a tap does
mountDie(document.getElementById("shown"), { sides: 6, face: 5, rollable: false, onePip: "black" });It keeps one steady square (small 48 pixels, medium 96, large 150, or width): the tumble moves only the picture inside it, so nothing on the page moves. Nothing on it can be selected, and a device that asks for reduced motion gets no tumble. It is silent unless you say sound: true, because a die on somebody's page has not been asked to make noise.
| Option | Default | What it does |
|---|---|---|
sides | 6 | 2 to 1000, or "F" for a Fate die |
faces | none | A die of your own, as in notation's d[Yes,No]: [{ label: "Yes" }, { label: "No" }] |
face | the top face | The face showing at first |
rollable | true | false is a die that only shows a face |
size, width | "medium" | "small", "medium" or "large"; or a width in pixels, which wins |
onePip | "red" | The colour of a d6's one pip: "red" or "black" |
source | cryptographic | seededSource("table") throws the same faces for everyone |
animationMs | 600 | The tumble; reduced motion always skips it |
sound, playSound | false | Whether a throw makes the sound of dice, and a sound of your own |
onRoll | none | Called with the face once it has landed |
cloth, theme, locale, strings | As on the tray |
The handle has roll(), show(face), setSides(sides, face?, faces?), setOnePip(colour), setLocale(locale), setRollable(on), destroy(), and face, rolling and element.
As a tag, from the same script as <korokoro-roller>:
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/korokoro@1/dist/element-define.js"></script>
<korokoro-die sides="20" size="large"></korokoro-die>
<korokoro-die sides="6" face="5" rollable="off"></korokoro-die>| Attribute | What it does |
|---|---|
sides, face | The kind of die, and the face showing |
rollable="off" | A die that shows and does not roll; rollable="on" makes it roll again |
size, width | small, medium, large, or pixels |
one-pip | red (unless said) or black |
lang, seed, sound, animation-ms, cloth | As the options of the same names; sound="on" for dice sounds |
Each throw is a korokoro-die-roll event with the face as its detail; changing face shows that face at once. The demo has one of each.
Embed it on any site
A tray on a page you do not build: a blog, a wiki, a forum. Choose how much of it you want.
| Size | What is shown | Room at 360px wide |
|---|---|---|
small | The felt and the result, for the dice you name. Tap to roll | about 540px tall |
medium | Those, and the choice of dice and bonus | about 1320px |
large | Everything: history, stats and odds too. Side by side from 900px wide | about 1600px |
An iframe, where the page allows no scripts:
<iframe src="https://johnmorrisdotca.github.io/korokoro/embed/?dice=2d6%2B3&size=small" title="Korokoro" width="360" height="540" style="border:0;max-width:100%" loading="lazy"></iframe>The address takes dice (any notation), size, lang (en or ja), sound=off, seed, and the colours felt and ink as #rrggbb. The page tracks nothing and loads nothing from anywhere else; a large tray keeps its history on the visitor's own device. Each roll is sent to the page that frames it: { korokoro: "roll", notation, total, dice } by postMessage.
One tag, where the page may run a script, with nothing to install:
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/korokoro@1/dist/element-define.js"></script>
<korokoro-roller notation="2d6+3" size="small"></korokoro-roller>The demo writes both for the dice you last rolled there, with a look at each size.
6. Svelte and Angular
The same one call in the framework's mount hook, and destroy() on the way out:
<script>
import { onMount } from "svelte";
import { mountRoller } from "@johnmorrisdotca/korokoro";
let box;
onMount(() => {
const roller = mountRoller(box);
return () => roller.destroy();
});
</script>
<div bind:this={box}></div>// Angular: in a standalone component with <div #box></div> in its template
private box = viewChild.required<ElementRef<HTMLElement>>("box");
constructor() {
afterNextRender(() => (this.roller = mountRoller(this.box().nativeElement)));
}
ngOnDestroy() {
this.roller?.destroy();
}Each of the six (Vue, Svelte, Angular, React, the web component and a plain page) is built from the packed tarball and rolled in Chromium and WebKit by scripts/check-frameworks.mjs before a release names it.
What a developer gets
- Typed results. TypeScript types for everything, with a doc comment on every export.
- A random source you can replace. The default is the platform's cryptographic generator;
seededSource("any text")is reproducible; and anything with anext()that returns a 32-bit number will do. - No dependencies, ES modules, a
defaultexport condition for tools that resolve from CommonJS, andsideEffects: false, so a bundler drops what you do not import. - Sizes. Rolling, notation and odds alone are about 16 kB minified (6 kB gzipped) once a bundler has shaken the rest out. With the tray it is about 98 kB (35 kB gzipped). The recorded sounds are another 36 kB (23 kB gzipped), fetched only when a roll first needs them.
- Where it runs. Browsers from Chrome and Edge 111, Firefox 113 and Safari 16.2. The core runs in Node 20 and later, Deno and Bun.