Cross is a crossword rendering library with wrappers for React, Svelte, and vanilla web components — all sharing the same core API.
npm i @coyneop/crossRequires Bun for development (tests, builds). Consumers can use any Node + bundler (esbuild, Vite, Rollup, Webpack).
import { Engine, RenderType } from "@coyneop/cross";
const puzzle = {
width: 15,
height: 15,
cells: [
// Cell[] — { kind: "block", position } or { kind: "value", position, value }
],
gridIndex: [], // computed automatically
};
const el = document.getElementById("grid")!;
const engine = new Engine(el, puzzle, RenderType.Canvas);
// Listen to user interaction
engine.on("select", (e) => console.log("selected", e.position));
engine.on("keydown", (e) => console.log("letter", e.letter, "at", e.position));
// Swap renderers at runtime
engine.setRenderer(RenderType.Svg);Import paths for tree-shaking:
import { Engine } from "@coyneop/cross";
// Renderers are bundled together in the core entry.Use the <cross-word> custom element. It works in any framework (or none).
<cross-word
id="grid"
renderer="canvas"
style="display: block; width: 75%; height: 75%"
></cross-word>
<script type="module">
import "@coyneop/cross/component/define"; // side-effect: registers <cross-word>
const board = document.getElementById("grid");
board.puzzle = {
width: 15,
height: 15,
cells: [
/* Cell[] */
],
gridIndex: [],
};
// Engine events surface as DOM CustomEvents
board.addEventListener("cross-select", (e) => console.log(e.detail));
board.addEventListener("cross-keydown", (e) => console.log(e.detail));
// Switch renderer via attribute
board.setAttribute("renderer", "svg");
</script>For full control over the tag name:
import { defineCross } from "@coyneop/cross/component";
defineCross("my-crossword");renderer attribute accepts "html", "canvas", "svg" (and "webgpu" in the future).
import { Crossword } from "@coyneop/cross/react";
import { RenderType } from "@coyneop/cross";
function App() {
const [puzzle, setPuzzle] = useState({
width: 15,
height: 15,
cells: [
/* Cell[] */
],
gridIndex: [],
});
return (
<Crossword
state={puzzle}
renderer={RenderType.Canvas}
style={{ width: "100%", height: "100%" }}
/>
);
}The renderer prop accepts any RenderType string. The component handles
mounting, resizing, and cleanup automatically.
<script lang="ts">
import Crossword from "@coyneop/cross/svelte";
import { RenderType } from "@coyneop/cross";
let puzzle = {
width: 15,
height: 15,
cells: [ /* Cell[] */ ],
gridIndex: [],
};
let renderer = RenderType.Html;
</script>
<Crossword {state} {renderer} class="my-grid" />The component accepts state (Puzzle), renderer (RenderType), and an optional
class. Mount/teardown and resize observation are handled automatically.
| Renderer | Import path | Description |
|---|---|---|
| HTML | Renderer.Html |
CSS Grid of <div> elements. Best accessibility, inspectable DOM. |
| Canvas | Renderer.Canvas |
2D canvas via OffscreenCanvas + ImageBitmap. Smooth, fast repaints. |
| SVG | Renderer.Svg |
SVG with <rect> + <text> elements. Scales cleanly. |
Each demo is a self-contained HTML page you run with Bun's dev server.
bun ./demo/index.html
# Opens at http://localhost:3000bun ./demo/component.htmlbun ./demo/react.htmlbun ./demo/svelte.htmlAll demos render a sample 15×15 grid and let you toggle between renderers via a toolbar in the top-left corner.
bun install # install dependencies
bun run check # typecheck + tests + coverage
bun run build # build dist/
bun test # run tests (with happy-dom)
bun test --watch # watch mode