import { $, ready, parseHTML, isElement } from "koyojs";
import { $, ready } from "koyojs/DOM";
Every entry point needs a document. In Node, Bun, or an edge runtime they throw KoyoDOMError rather than failing with a bare ReferenceError, so a server-rendered path can catch and skip the DOM work instead of crashing the render.
Selects, wraps, or creates elements and returns a chainable collection. A string starting with `<` and ending with `>` is parsed as HTML; anything else is treated as a CSS selector. Also accepts an Element, Document, NodeList, array, or another collection. Passing `null` or `undefined` gives an empty collection rather than throwing, so optional lookups chain without guards. Requires a document — in Node, Bun, or an edge runtime it throws `KoyoDOMError` instead of failing with a bare `ReferenceError`.
| Parameter | Type | Description |
|---|
target | Selectable | CSS selector, HTML string, Element, Document, NodeList, array, or collection. |
context | Selectable | Optional root to scope the search to. |
| returns | KoyoCollection | — |
import { $ } from "koyojs/DOM";
$("li.done").addClass("struck").css("opacity", 0.5);
$("#log").append($("<p class='note'>saved</p>"));
$(event.target).closest("tr").remove();
$(".cell", row).text("");
$(document.querySelector("#maybe-missing")).addClass("active");
if ($(".error").length > 0) showBanner();
KoyoCollection
The chainable result of $(). Methods that read return a value from the first element; methods that write apply to every element and return the collection, so calls chain.
Every traversal method returns a new collection — the original is never mutated, so you can branch off a base selection freely.
| Method | Returns | Description |
|---|
length | number | How many elements the collection holds. |
toArray() | Element[] | The matched elements as a plain array. |
get(index) | Element | undefined | The element at an index, or undefined. |
each(fn) | this | Runs fn per element. Returning false stops iteration, as in jQuery. |
filter(selectorOrFn) | KoyoCollection | Keeps elements matching a selector or predicate. |
is(selector) | boolean | True when any element matches the selector. |
find(selector) | KoyoCollection | Descendants matching the selector. |
closest(selector) | KoyoCollection | Nearest ancestor (or self) matching the selector. |
parent() | KoyoCollection | Direct parents. |
children(selector?) | KoyoCollection | Direct children, optionally filtered. |
siblings(selector?) | KoyoCollection | Siblings, optionally filtered. |
add(target) | KoyoCollection | Union with another selection. |
eq(index) | KoyoCollection | A single-element collection at an index. |
first() | KoyoCollection | First element as a collection. |
last() | KoyoCollection | Last element as a collection. |
import { $ } from "koyojs/DOM";
$("#table").on("click", ".delete", function () {
$(this).closest("tr").remove();
});
const rows = $("#table tr");
rows.filter(".selected").addClass("highlight");
rows.filter((el, i) => i % 2 === 0).addClass("even");
$(".item").each((el, i) => {
if (i >= 10) return false;
el.setAttribute("data-index", String(i));
});
$(".tab").eq(2).addClass("active");
$(".field").first().is(":focus");
Thin wrappers over classList, applied across every element in the collection.
| Method | Returns | Description |
|---|
addClass(...names) | this | Adds one or more classes to every element. |
removeClass(...names) | this | Removes one or more classes from every element. |
toggleClass(name, force?) | this | Toggles a class, or forces it on/off with the second argument. |
hasClass(name) | boolean | True when any element carries the class. |
import { $ } from "koyojs/DOM";
$(".card").addClass("loaded", "visible");
$(".card").removeClass("skeleton");
$("#menu").toggleClass("open", isOpen);
if ($("#form").hasClass("dirty")) confirmLeave();
Getters read from the first element; setters write to every element. That split is what lets the same name serve both roles without an options object.
| Method | Returns | Description |
|---|
attr(name) | string | null | Reads an attribute from the first element. |
attr(name, value) | this | Sets an attribute on every element. null removes it. |
attr(values) | this | Sets several attributes at once from an object. |
removeAttr(...names) | this | Removes attributes from every element. |
prop(name, value) | this | Sets a DOM property (checked, disabled, …) rather than an attribute. |
data(key) | string | undefined | Reads a data-* attribute from the first element. |
data(key, value) | this | Writes a data-* attribute on every element. |
val() | string | Reads the value of the first form control. |
val(value) | this | Sets the value of every form control. |
import { $ } from "koyojs/DOM";
$("#avatar").attr("src");
$("img.lazy").attr("loading", "lazy");
$("#link").attr({ href: "/docs", title: "Docs" });
$(".temp").removeAttr("data-temp", "hidden");
$("#agree").prop("checked", true);
$("#submit").prop("disabled", false);
$("#row").data("id");
$("#row").data("state", "editing");
const email = $("#email").val();
$("#search").val("");
A plain string is treated as text and only parsed as markup when it looks like HTML, so `.append("5 < 6")` can never inject an element. With several targets, all but the last receive clones — a node can only live in one place, and that is what makes `$(".row").append(icon)` mean anything.
| Method | Returns | Description |
|---|
text() | string | Text content of the first element. |
text(value) | this | Sets text content on every element. |
html() | string | innerHTML of the first element. |
html(value) | this | Sets innerHTML on every element. |
append(content) | this | Inserts as the last child. |
prepend(content) | this | Inserts as the first child. |
before(content) | this | Inserts as the previous sibling. |
after(content) | this | Inserts as the next sibling. |
remove() | this | Detaches every element from the document. |
empty() | this | Removes all children. |
clone(deep?) | KoyoCollection | Copies the elements. Deep by default. |
import { $ } from "koyojs/DOM";
$("#title").text();
$("#title").text("Dashboard");
$("#out").append(userSuppliedString);
$("#out").append("5 < 6");
$("#list").append("<li>New item</li>");
$(".row").append($("<span class='icon'>★</span>"));
$(".stale").remove();
$("#container").empty();
const template = $("#row-template").clone();
`.css()` takes numbers and appends px, except on unitless properties (opacity, zIndex, lineHeight, flexGrow, …) and custom `--properties`. `.hide()` remembers the inline display it replaced so `.show()` restores it, falling back to block when a stylesheet is what hides the element.
| Method | Returns | Description |
|---|
css(property) | string | Computed value of a property on the first element. |
css(property, value) | this | Sets one property on every element. |
css(properties) | this | Sets several properties at once. |
hide() | this | Hides elements, remembering the display value replaced. |
show() | this | Restores the remembered display, or falls back to block. |
toggle(force?) | this | Flips visibility, or forces it with the argument. |
import { $ } from "koyojs/DOM";
$("#box").css("width");
$("#box").css("width", 240);
$("#box").css("opacity", 0.5);
$("#box").css("--brand", "#71A6D1");
$(".panel").css({
marginTop: 16,
zIndex: 10,
background: "white",
});
$("#modal").hide();
$("#modal").show();
$("#sidebar").toggle(isExpanded);
on/once/off are overloaded on HTMLElementEventMap, so `on("click", e => …)` gives a MouseEvent rather than a bare Event. Listener bookkeeping lives in a WeakMap keyed by element, so `.off()` works from a different collection object than the one `.on()` was called on.
| Method | Returns | Description |
|---|
on(type, handler, options?) | this | Binds a handler to every element. |
on(type, selector, handler, options?) | this | Delegated: binds once on the root, fires for matching descendants. |
once(type, handler, options?) | this | Binds a handler that runs at most once. |
once(type, selector, handler, options?) | this | Delegated one-shot handler. |
off() | this | Removes every handler bound through Koyo. |
off(type, handler?) | this | Removes handlers for a type. |
off(type, selector, handler?) | this | Removes delegated handlers. |
trigger(type, detail?, options?) | this | Dispatches an event, with optional CustomEvent detail. |
import { $ } from "koyojs/DOM";
$("#save").on("click", (e) => {
e.preventDefault();
save();
});
$("#table").on("click", ".row", function (e) {
$(this).toggleClass("selected");
});
$(document).once("keydown", ".shortcut", handleShortcut);
$("#table").off("click", ".row");
$("#widget").off();
$("#form").trigger("submit");
$("#cart").trigger("cart:updated", { items: 3 });
Runs `fn` once the document has been parsed. Calls it synchronously when the document is already parsed, so a late registration is never dropped. Waits for `DOMContentLoaded`, not `load`, so it does not block on images and stylesheets.
| Parameter | Type | Description |
|---|
fn | () => void | Callback to run when the document is ready. |
| returns | void | — |
import { ready, $ } from "koyojs/DOM";
ready(() => {
$("#app").text("ready");
$("#menu").on("click", ".item", handleSelect);
});
setTimeout(() => ready(() => console.log("not dropped")), 5000);
Parses an HTML string into detached elements. Uses a `<template>`, so fragments that are invalid outside their parent — a bare `<tr>`, `<td>`, or `<option>` — survive instead of being stripped, which is what happens with `innerHTML` on a `<div>`. Text and comment nodes at the top level are dropped; only elements are returned, fully detached with a null `parentNode`.
| Parameter | Type | Description |
|---|
html | string | The markup to parse. |
| returns | Element[] | — |
import { parseHTML, $ } from "koyojs/DOM";
parseHTML("<li>a</li><li>b</li>");
const rows = parseHTML("<tr><td>1</td></tr>");
$("#table tbody").append(rows);
const opts = parseHTML("<option value='1'>One</option>");
$("#select").append(opts);
parseHTML("<p>x</p>")[0].parentNode;
Duck-typed element check using `nodeType === 1`. `value instanceof Element` is false for a node from another realm — an iframe, a worker, or a second copy of the DOM shim in tests — so the nodeType check is used instead.
| Parameter | Type | Description |
|---|
value | unknown | The value to test. |
| returns | value is Element | — |
import { isElement } from "koyojs/DOM";
isElement(document.body);
isElement("<p>not a node</p>");
isElement(null);
const node = iframe.contentDocument.body;
node instanceof Element;
isElement(node);
function mount(target: unknown) {
if (!isElement(target)) throw new TypeError("mount needs an element");
target.append(view);
}