Campfire helps you write crisp, reactive UI with vanilla JS/TS—no build step, no boilerplate, no magic.
import { nu } from "@campfire/core";
const [button] = nu("button#go")
.content("Open!")
.on("click", () => open("https://campfire.js.org"))
.done();
import { nu, store } from "@campfire/core";
const name = store({ value: "Sandy" });
const [greeting] = nu("div")
.deps({ name })
// render is called every time one of the deps changes
.render(({ name }, { b }) => b.html`Hello <b>${name}</b>!`)
.done();
import { nu, store } from "@campfire/core";
// A name badge component
const NameBadge = () => {
const name = store({ value: "" });
return nu("div.badge")
.deps({ name })
.render(({ name }, { b }) => b.content(`Hi, ${name}`));
};
Is this a framework? Nope—just tools that make vanilla JS delightful.
How do I make components? Return an element (or a function that returns one). That's it.
Why are escape/unescape so simple? They’re for basic safety; bring your own sanitizer for anything fancy.
You can include campfire into your site with just an import statement, either from a location where you are hosting it, or from esm.sh / unkpg.
import cf from "https://esm.sh/campfire.js@4";
or
import { ListStore, nu } from "https://esm.sh/campfire.js@4";
If you use a bundler or want to write in TypeScript, you can install Campfire from npm. This gives you full TypeScript support as well as TSDoc comments.
To install:
npm install --save-dev campfire.js
Then in your code:
import cf from "campfire.js";
Good luck, and thank you for choosing Campfire!
View the full API reference for detailed descriptions of the methods and classes provided by Campfire.
Campfire provides the following methods and classes:
nu()
- element creation helperCreate DOM elements with a readable, chainable builder pattern.
import cf from "@campfire/core";
const [greeting] = cf.nu("h1")
.content("Hello, world!")
.attr("id", "greeting")
.done();
const name = cf.store({ value: "User" });
const [info] = cf.nu("div.info")
.deps({ name })
.render(({ name }, { b }) => b.html`Logged in as: <strong>${name}</strong>`)
.done();
store()
- reactive data storesStore state in a reactive scalar, array, or map.
import cf from "@campfire/core";
const counter = cf.store({ value: 0 });
counter.update((n) => n + 1);
counter.on("update", (e) => console.log(e.value));
const todos = cf.store({ type: "list", value: ["A"] });
todos.push("B");
const users = cf.store({ type: "map", value: { sid: true } });
users.set("alex", false);
select()
- element selectionSelect DOM elements with a unified API.
import cf from "@campfire/core";
const [header] = cf.select({ s: "header" });
const buttons = cf.select({ s: "button", all: true });
insert()
- element insertionInsert elements anywhere in the DOM.
import cf from "@campfire/core";
const [greeting] = cf.nu("h1")
.content("Hello, world!")
.done();
const info = cf.nu("p")
.content("This element was generated using Campfire v4")
.done();
// insert a single HTMLElement...
cf.insert(greeting, { into: document.body });
// or insert the result of nu() directly
cf.insert(info, { after: greeting });
html()
- HTML templatingEscape content for the DOM and compose structured HTML.
import cf from "@campfire/core";
const user = "<script>alert('xss')</script>";
const text = cf.html`A message for <b>${user}</b>`; // user is escaped safely
mustache()
& template()
- string templatesimport cf from "@campfire/core";
// Basic interpolation (auto-escaped)
cf.mustache("Hello, {{name}}!", { name: "<b>Alex</b>" }); // "Hello, <b>Alex</b>!"
// Unescaped HTML (triple braces)
cf.mustache("Raw: {{{html}}}", { html: "<b>bold</b>" }); // "Raw: <b>bold</b>"
// Sections (conditional, loop)
cf.mustache(
"{{#items}}<li>{{.}}</li>{{/items}}{{^items}}No items{{/items}}",
{ items: ["Red", "Green"] },
); // "<li>Red</li><li>Green</li>"
// Reusable (compiled) templates
const hello = cf.template("Hello, {{who}}!");
hello({ who: "World" }); // "Hello, World!"
hello({ who: "<x>" }); // "Hello, <x>!"
extend()
& x()
- element modificationModify or enhance any DOM element with props, reactivity, and more. Use the builder for clarity.
import cf from "@campfire/core";
const header = document.createElement("header");
cf.x(header)
.content("Page Header")
.style("fontWeight", "bold")
.done();
empty()
and rm()
- element cleanupClear an element or remove it from the DOM.
import cf from "@campfire/core";
const header = cf.nu("header").content("foo").ref();
cf.empty(header);
cf.rm(header);
seq()
& ids()
- ranges and unique idsimport cf from "@campfire/core";
cf.seq(3).forEach((i) => {
const id = cf.ids("item")();
cf.nu("div.item").attr("id", id).content(`Item #${i + 1}`).done();
});