Skip to content

JavaScript API

partialkit/auto registers every component and starts the observer on load — that is what the drop-in build does. Import partialkit instead when you want to choose.

import { register, start, dialog, dropdownMenu } from "partialkit";
register(dialog);
register(dropdownMenu);
start();

The components are carousel, checkbox, contextMenu, dialog, dropdownMenu, hoverCard, inputOtp, menubar, popover, slider, tabs, theme, toggle and tooltip. Everything else is CSS only and needs no registration.

ExportPurpose
register(component)Adds a component and runs its setup once
start({ root })Mounts everything under root and observes it. Defaults to document.body
mount(root) / unmount(root)Manual control, if you would rather not observe
stop()Disconnects the observer and runs every cleanup
openDialog(id, opener?) / closeDialog(id, value?)Programmatic dialog control
getTheme() / setTheme(theme) / applyTheme()Theme control
ensureId, setDefaultAttribute, focusIsLoose, focusableWithin, focusFirst, createTypeaheadARIA helpers
findAll, closestWithAttribute, dispatchDOM helpers
anchorOf, position, positionAgainst, trackPlacing a floating panel against a trigger or a point
EventFired onDetail
pk:dialog:before-openthe dialog, cancelable
pk:dialog:openthe dialog
pk:dialog:closethe dialog{ returnValue }
pk:menu:changethe item{ checked, value }
pk:tabs:changethe trigger{ value }
pk:toggle:changethe button{ pressed, value }
pk:carousel:changethe carousel{ index, atStart, atEnd }
pk:otp:changethe field{ value, complete }
pk:theme:change<html>{ theme, dark }

A component is an object with a name, a selector, and a mount that runs once per matching element. Return a cleanup and it runs when that element leaves the DOM.

import { register, type Component } from "partialkit";
const counter: Component = {
name: "counter",
selector: "[data-counter]",
mount(element) {
const onClick = () => (element.textContent = String(Number(element.textContent) + 1));
element.addEventListener("click", onClick);
return () => element.removeEventListener("click", onClick);
},
};
register(counter);

setup() is the other half of the contract: it runs once when the component is registered, and is where document-level delegation belongs.

const shortcuts: Component = {
name: "shortcuts",
selector: "[data-shortcut]",
mount() {},
setup() {
const onKeydown = (event: KeyboardEvent) => { /* ... */ };
document.addEventListener("keydown", onKeydown);
return () => document.removeEventListener("keydown", onKeydown);
},
};

Prefer setup for anything that can be delegated. It costs one listener no matter how many elements arrive, which is what keeps the runtime cheap on pages that replace markup often.