Lightweight JavaScript library for DOM querying, traversal, manipulation, events, animation, AJAX, and browser utilities.
- Prebuilt ESM and UMD bundles in
dist/ - Browser UMD bundle exposed as
globalThis.fQueryandglobalThis.$ - Familiar, chainable
QuerySetAPI alongside equivalent static functions - Querying across elements, documents, fragments, shadow roots, collections, and multiple contexts
- DOM traversal, manipulation, attributes, styles, events, selection, and form helpers
- Promise-like AJAX requests and animations, with animation queues and built-in effects
- Dynamic script and stylesheet loading, sanitization, cookies, and browser utilities
@fr0st/corehelpers exposed with an_prefix- JSDoc-powered IntelliSense
npm i @fr0st/queryfQuery's package entry point is ESM-only. In a browser environment, importing the default query function initializes the current window and document and assigns window.$.
import $ from '@fr0st/query';
$('.card').addClass('is-ready');The ESM bundle imports @fr0st/core. Map that dependency when loading the bundle directly in a browser:
<script type="importmap">
{
"imports": {
"@fr0st/core": "https://cdn.jsdelivr.net/npm/@fr0st/core@latest/dist/frost-core.esm.min.js"
}
}
</script>
<script type="module">
import $ from 'https://cdn.jsdelivr.net/npm/@fr0st/query@latest/dist/fquery.esm.min.js';
$('.card').addClass('is-ready');
</script>Load the bundle from your own copy or a CDN:
<script src="/path/to/dist/fquery.min.js"></script>
<!-- or -->
<script src="https://cdn.jsdelivr.net/npm/@fr0st/query@latest/dist/fquery.min.js"></script>
<script>
$('.card').addClass('is-ready');
</script>The UMD bundle includes @fr0st/core and exposes the same object as both globalThis.fQuery and globalThis.$. If another library already uses $, restore it while continuing to use fQuery:
fQuery.noConflict();
fQuery('.card').addClass('is-ready');The package root resolves to the prebuilt ESM bundle. Published files under dist/ and src/ are also available through matching package subpaths.
Outside a browser, the default export is an initializer. Pass it a Window, such as one created by JSDOM:
import { JSDOM } from 'jsdom';
import register from '@fr0st/query';
const { window } = new JSDOM('<main><p class="message">Hello</p></main>');
const $ = register(window);
$('.message').setText('Hello from fQuery');The DOM implementation is supplied by the application and is not a dependency of fQuery.
Query existing nodes and chain operations:
const cards = $('.card')
.addClass('is-ready')
.setAttribute('aria-busy', 'false');
cards.find('.title').setText('Ready');
cards.addEvent('click.cards', (event) => {
$(event.currentTarget).toggleClass('is-active');
});Create nodes from HTML and insert them into the document:
const notice = $('<aside class="notice">Saved</aside>');
notice
.appendTo(document.body)
.fadeIn({ duration: 200 });Run code when the document is ready:
$(() => {
$('[data-autofocus]').focus();
});For node or selector inputs, $(selector, context?) and $.query(selector, context?) return a QuerySet. $.queryOne(selector, context?) returns a QuerySet containing at most one node.
A selector can be:
- a CSS selector string;
- an HTML string beginning with
<; - a
Node,DocumentFragment,ShadowRoot,Document, orWindow; - a
NodeList,HTMLCollection,QuerySet, or array of nodes; or - for
$()and$.query(), a callback to run when the document is ready instead of returning a set.
The optional context can be a selector or one or more element, fragment, shadow-root, or document contexts. It defaults to the configured document.
const form = $('#account-form');
const formFields = $('input, select', form);
const firstError = $.queryOne('.error', form);
console.log(formFields.length);
console.log(firstError.get(0));A QuerySet is iterable and keeps an ordered collection of nodes. Traversal and filtering methods return new sets; DOM mutation methods are chainable and generally return the current set.
new $.QuerySet(nodes?): construct a set directly.query.length: return the number of nodes.query.get(index?): return one node, including negative indexes, or all nodes when no index is given.query.each(callback): run a callback for each node and return the current set.query.map(callback): map the nodes into a newQuerySet.query.slice(begin?, end?): return a slicedQuerySet.query.add(selector, context?): add nodes and return a new sorted, deduplicated set.query.eq(index): return the node at an index as a new set.query.first()/query.last(): return the first or last node as a new set.query[Symbol.iterator](): iterate over the contained nodes.
Most operations are available in two forms:
$.addClass(selector, 'active');
$(selector).addClass('active');Unless an entry shows full signatures, the static form takes the target selector as its first argument and the QuerySet form omits that argument. A nodeFilter can be a CSS selector, node, node collection, QuerySet, array, or callback.
$.find(selector, context?)/query.find(selector): find all matching descendants.$.findOne(selector, context?)/query.findOne(selector): find the first matching descendant.$.findByClass(className, context?)/query.findByClass(className): find descendants by class.$.findOneByClass(className, context?)/query.findOneByClass(className): find the first descendant by class.$.findById(id, context?)/query.findById(id): find descendants by ID.$.findOneById(id, context?)/query.findOneById(id): find the first descendant by ID.$.findByTag(tagName, context?)/query.findByTag(tagName): find descendants by tag name.$.findOneByTag(tagName, context?)/query.findOneByTag(tagName): find the first descendant by tag name.
child(nodeFilter?): return the first matching child.children(nodeFilter?, { elementsOnly? }): return matching children.contents(): return children including text and comment nodes.closest(nodeFilter?, limitFilter?): return the closest matching ancestor, excluding the node itself and the limit.parent(nodeFilter?): return matching direct parents.parents(nodeFilter?, limitFilter?): return matching ancestors up to an optional limit.next(nodeFilter?)/prev(nodeFilter?): return the next or previous element sibling if it matches the filter.nextAll(nodeFilter?, limitFilter?)/prevAll(nodeFilter?, limitFilter?): return following or preceding matching element siblings before the limit.siblings(nodeFilter?, { elementsOnly? }): return matching siblings.commonAncestor(): return the nearest shared ancestor, excluding the input nodes themselves. The static form returnsundefinedwhen none exists; the QuerySet form returns an empty set.offsetParent(): return the first node's positioned offset parent.fragment(): return the first node'sDocumentFragment.shadow(): return the first node'sShadowRoot.
The static forms of children, nextAll, parents, and prevAll also accept { first: true } to stop at the first matching result for each input node.
Filtering methods return matching nodes; their is... and has... counterparts return booleans.
filter(nodeFilter)/filterOne(nodeFilter): nodes matching a filter.not(nodeFilter)/notOne(nodeFilter): nodes not matching a filter.connected()/isConnected(): connection to a document, including through a shadow tree.equal(otherSelector)/isEqual(otherSelector, { shallow? }): DOM equality.same(otherSelector)/isSame(otherSelector): node identity.fixed()/isFixed(): fixed positioning on a node or ancestor.hidden()/visible(): visibility filters.isHidden()/isVisible(): visibility tests.is(nodeFilter): whether any node matches a filter.withAnimation()/hasAnimation(): active fQuery animations.withAttribute(attribute)/hasAttribute(attribute): attribute presence.withChildren()/hasChildren(): child-element presence.withClass(...classes)/hasClass(...classes): class presence.withCssAnimation()/hasCssAnimation(): at least one nonzero computed animation duration.withCssTransition()/hasCssTransition(): at least one nonzero computed transition duration.withData(key?)/hasData(key?): fQuery custom data.hasDataset(key): dataset presence.withDescendent(nodeFilter?)/hasDescendent(nodeFilter?): matching descendants, excluding the node itself. Omitting the filter checks for any descendant element.withProperty(property)/hasProperty(property): own-property presence.hasFragment()/hasShadow(): fragment or shadow-root presence.
Element visibility is based on the presence of layout rectangles, so fixed-position elements can be visible. opacity: 0 and visibility: hidden do not by themselves make these helpers report an element as hidden. Document and Window visibility uses the document's visibility state.
Getter methods read the first matching node. Setter and removal methods apply to each matching node.
getAttribute(attribute?)/setAttribute(attribute, value)/removeAttribute(attribute): attributes.getProperty(property)/setProperty(property, value)/removeProperty(property): JavaScript properties.getDataset(key?)/setDataset(key, value)/removeDataset(key): parsed and serializeddatasetvalues.getHtml()/setHtml(html): HTML content.getText()/setText(text): text content.getValue()/setValue(value): form-control values.
Omitting the key from getAttribute() or getDataset() returns all values. setAttribute(), setProperty(), and setDataset() also accept an object of keys and values. hasDataset() checks attribute presence, including empty values.
Custom data is stored separately from DOM attributes and dataset.
getData(key?): read one value or the full data object from the first node.setData(key, value): set a custom value on every node.removeData(key?): remove one value or all custom data.cloneData(otherSelector): copy each source node's custom data to every destination; later sources take precedence for shared keys.
setData() also accepts an object of keys and values. Data copies are shallow, so stored object values remain shared.
addClass(...classes)/removeClass(...classes)/toggleClass(...classes): change classes. Arrays and space-separated strings are accepted.css(style?): read one computed CSS value, or all values when omitted, from the first node.getStyle(style?): read one inline style value, or all values when omitted, from the first node.setStyle(style, value, { important? }): set one or more inline styles.setStyleLock(property, value, { important? }): temporarily set and lock one inline property on every matched element. Returns a release function.removeStyle(style): remove an inline style.hide()/show()/toggle(force?): change element visibility. Passtruetotoggle()to show orfalseto hide; omit it to toggle the current state.
CSS custom property names are preserved verbatim, including case: --brandColor and --brand-color are distinct properties.
Style locks accept supported longhand properties and CSS custom properties. Shorthands, aliases, invalid values, and attempts to lock an already locked property throw before any matched element is changed. Property names and numeric values use the same normalization as setStyle().
Locks also throw before changing any matched element when an existing longhand value cannot be restored, such as padding-left supplied by an inline padding: var(--spacing) shorthand. Locks that would reorder an existing declaration also throw, such as locking width declared before inline-size. Empty custom properties remain supported.
const release = $('.panel').setStyleLock('display', 'none');
release(); // Restore each element's original inline value and !important priority.The static form is $.setStyleLock(selector, property, value, options?). Use release({ restore: false }) to unlock while keeping the current declaration. Calling the release function again does nothing. Locks are cooperative: ordinary setStyle() calls and direct DOM writes can still change the property, and releasing restores the original declaration by default. Different properties can be locked independently.
hide() holds a display lock until show() releases it, preserving the original inline display value and priority across repeated hides and toggle cycles. Hiding an element whose display is already locked by another caller throws; show() only releases locks created by hide().
height() and width() accept { boxSize, outer }. Use $.CONTENT_BOX, $.PADDING_BOX, $.BORDER_BOX, $.MARGIN_BOX, or $.SCROLL_BOX for boxSize.
height(options?)/width(options?): read the first node's computed dimensions.center({ offset? }): return center coordinates.position({ offset? }): return positioned coordinates.rect({ offset? }): return the bounding rectangle.constrain(containerSelector): keep nodes within a container.distTo(x, y, { offset? })/distToNode(otherSelector): calculate distances.nearestTo(x, y, { offset? })/nearestToNode(otherSelector): return the nearest node.percentX(x, { offset?, clamp? })/percentY(y, { offset?, clamp? }): convert a coordinate to a percentage of the node.getScrollX()/getScrollY(): read scroll coordinates.setScroll(x, y)/setScrollX(x)/setScrollY(y): set scroll coordinates.
Methods accepting otherSelector also accept nodes, collections, QuerySets, arrays, and—where creation is supported—HTML strings.
append(otherSelector)/prepend(otherSelector): insert content inside each target.appendTo(otherSelector)/prependTo(otherSelector): insert each target inside another node.before(otherSelector)/after(otherSelector): insert content adjacent to each target.insertBefore(otherSelector)/insertAfter(otherSelector): insert each target adjacent to another node.replaceWith(otherSelector)/replaceAll(otherSelector): replace targets or other nodes.wrap(otherSelector)/wrapAll(otherSelector)/wrapInner(otherSelector): wrap nodes or their contents.unwrap(nodeFilter?): remove matching parents while preserving their contents.clone({ deep?, events?, data?, animations? }): clone nodes and optionally their fQuery state.detach(): remove nodes while preserving associated state.remove(): remove nodes and their associated state.empty(): remove all child nodes and their associated state.attachShadow({ open? }): attach a shadow root to the first node.
Cloning is deep by default; copying events, data, and animations is opt-in. Deep cloning includes requested state inside template.content. setHtml() replaces a template's content, while empty() and setText() affect its own child nodes. Cleanup preserves state on surviving shadow trees and template content fragments.
Create nodes without a target:
$.create(tagName?, options?): create an element with optionalhtml,text,class,style,value,attributes,properties, anddataset.$.createComment(comment): create a comment node.$.createText(text): create a text node.$.createFragment(): create a document fragment.$.createRange(): create a range.
Event names may include namespaces such as click.menu. Returning false from an event callback prevents the default action.
addEvent(events, callback, options?): add one or more event handlers.addEventOnce(events, callback, options?): add self-removing handlers.addEventDelegate(events, delegate, callback, options?): add delegated handlers.addEventDelegateOnce(events, delegate, callback, options?): add self-removing delegated handlers.removeEvent(events?, callback?, options?): remove matching handlers.removeEventDelegate(events?, delegate?, callback?, options?): remove matching delegated handlers.cloneEvents(otherSelector): copy registered handlers to other nodes.triggerEvent(events, options?): trigger events on every node.triggerOne(event, options?): trigger an event on the first node and return whether it was not cancelled, orundefinedfor an empty selection.blur()/click()/focus(): invoke the native action on the first node.
Listener options include capture and passive; the static $.addEvent() form also accepts delegate and selfDestruct. Trigger options include data, detail, bubbles, and cancelable.
Delegation matches the target or its nearest matching ancestor before the container. Scoped selectors use that container as :scope; Window delegation uses its document. During a delegated callback, event.currentTarget is the matching element and event.delegateTarget is the container.
$.mouseDragFactory(down, move?, up?, options?) creates a mouse/touch drag callback. Options include debounce, passive, preventDefault, and the required number of touches.
await $.fadeIn('.panel', { duration: 200 });
$('.meter').animate((node, progress) => {
node.style.width = `${progress * 100}%`;
}, { duration: 500 });Common animation options are:
| Option | Default | Description |
|---|---|---|
duration |
1000 |
Duration in milliseconds. |
type |
'ease-in-out' |
One of linear, ease-in, ease-out, or ease-in-out. |
infinite |
false |
Repeat indefinitely. |
debug |
false |
Expose timing values through dataset. |
start |
Current performance time | Start time in the configured Window's performance.now() clock. |
queueName |
'default' |
Queue used by QuerySet animation methods. |
animate(callback, options?): run a custom progress callback.dropIn(options?)/dropOut(options?): drop nodes from or toward a direction.fadeIn(options?)/fadeOut(options?): animate opacity.rotateIn(options?)/rotateOut(options?): animate a 3D rotation.slideIn(options?)/slideOut(options?): slide nodes from or toward a direction.squeezeIn(options?)/squeezeOut(options?): animate dimensions from or toward a direction.stop({ finish? }): stop active animations, finishing them by default.
Slide and drop effects use transforms for movement. Squeeze effects animate width or height and use transforms for positional offsets.
Slide, drop, and squeeze effects accept direction as top, right, bottom, left, or a function returning a direction. Slide and squeeze default to bottom; drop defaults to top. Rotation accepts x, y, and z axis components (defaulting to 0, 1, and 0) and inverse (defaulting to false).
Built-in effects lock the inline properties they change and restore them when they complete or are stopped with finish: true. Stopping with finish: false releases the locks while leaving the current animated styles in place. Failed effects release their locks and restore their original styles. Effects reject if a property is already locked or its original declaration cannot be safely restored; effects using different properties can run together.
Frame updates preserve existing inline !important priorities. Cloned effects retain the source animation's original declarations for restoration.
Static animation methods return an AnimationSet. QuerySet animation methods queue the work and return the current set. QuerySet stop() also clears all queues before stopping active animations.
new $.Animation(node, callback, options?) creates one promise-like animation. It supports then, catch, finally, clone(node), stop({ finish? }), and update(time?). new $.AnimationSet(animations) combines animations and supports then, catch, finally, and stop({ finish? }).
Use $.getAnimationDefaults() and $.setAnimationDefaults(options) to inspect or change defaults. $.useTimeout(true) selects the timer fallback instead of animation frames.
$('.notice')
.fadeIn({ duration: 150 })
.delay(500)
.fadeOut({ duration: 150 });$.queue(selector, callback, { queueName? })andquery.queue(callback, { queueName? })queue callbacks.$.clearQueue(selector, { queueName? })andquery.clearQueue({ queueName? })clear the default or named queue. Pass{ queueName: null }to clear all queues.query.delay(duration, { queueName? })queues a delay.
fQuery uses XMLHttpRequest and returns a promise-like AjaxRequest:
const request = $.get('/api/items', { page: 2 }, {
responseType: 'json',
});
const { response, xhr } = await request;$.ajax(options?): create a request with explicit options.$.get(url, data?, options?): send a GET request.$.post(url, data?, options?): send a POST request.$.put(url, data?, options?): send a PUT request.$.patch(url, data?, options?): send a PATCH request.$.delete(url, options?): send a DELETE request.
Request options include:
| Option | Default | Description |
|---|---|---|
url |
Current location | Request URL. |
method |
'GET' |
HTTP method. |
data |
null |
String, array, object, boolean, or FormData payload. |
contentType |
'application/x-www-form-urlencoded' |
Default Content-Type header, or false to omit the automatically generated header. |
responseType |
null |
Expected response type. |
mimeType |
— | MIME type override. |
username / password |
— | HTTP authentication values. |
timeout |
0 |
Timeout in milliseconds. |
cache |
true |
Add a cache-busting query value when disabled. |
processData |
true |
Encode object data using the final Content-Type header; GET and HEAD use query parameters. |
isLocal |
Auto-detected | Treat the request as local. |
rejectOnCancel |
true |
Reject cancellation through cancel() or xhr.abort(). |
headers |
{} |
Additional request headers, merged case-insensitively over defaults. |
beforeSend / afterSend |
null |
Hooks receiving the XMLHttpRequest. |
onProgress / onUploadProgress |
null |
Hooks receiving (progress, xhr, event). |
xhr |
XMLHttpRequest factory | Function returning the request object. |
GET and HEAD data is appended to the URL. Relative URLs, including cache-busting rewrites, resolve against the configured Window document's base URI. With processData: true, object request bodies are encoded using the final Content-Type header, ignoring parameters such as charset: JSON is stringified, URL-encoded data uses parseParams(), and other object data uses parseFormData(). Existing FormData is sent directly without an automatically generated Content-Type header.
AjaxRequest exposes its xhr, implements then, catch, and finally, and can be cancelled with request.cancel(reason?). Use $.getAjaxDefaults() and $.setAjaxDefaults(options) to inspect or change request defaults.
HTTP error statuses, network errors, and timeouts reject with { status, xhr, event }; cancellation rejects with { status, xhr, reason }. Setting rejectOnCancel: false leaves a cancelled request's promise pending.
$.parseParams(data) produces URL-encoded parameters and $.parseFormData(data) produces a FormData object. Both accept objects or { name, value } entries. parseFormData() preserves repeated names and File or Blob values.
$.loadScript(url, attributes?, options?): load one script. Scripts default to ordered execution.$.loadScripts(urls, options?): load multiple scripts. Entries can be URLs or attribute objects.$.loadStyle(url, attributes?, options?): load one stylesheet.$.loadStyles(urls, options?): load multiple stylesheets. Entries can be URLs or attribute objects.
Loader options include cache and an alternate document context. Each function returns a Promise.
Cookie helpers are $.getCookie(name), $.setCookie(name, value, { expires?, path?, secure? }), and $.removeCookie(name, { path?, secure? }). Cookie expiration is specified in seconds.
$.parseHtml(html): parse HTML into an array of elements; top-level text and comment nodes are discarded.$.parseDocument(input, { contentType? }): parse text into aDocument.$.sanitize(html, allowedTags?): remove disallowed elements and attributes from HTML.
The sanitizer's allowedTags argument maps lowercase tag names to arrays of allowed attribute names or regular expressions. String rules match exact attribute names, while the '*' entry applies attributes to every allowed tag. URI attributes using the javascript: protocol are removed; other protocols are not filtered.
When template is allowed, its content is sanitized recursively.
select()/selectAll(): select the first node or all target nodes.beforeSelection()/afterSelection(): insert nodes before or after the current selection.wrapSelection(): wrap the current selection with the target nodes.
Static-only helpers are $.getSelection() and $.extractSelection().
getSelection() includes selected text and comment nodes and returns an empty array for a collapsed selection. Partially selected nodes are returned as whole nodes. Empty or unmatched inputs to beforeSelection(), afterSelection(), and wrapSelection() leave the current selection intact.
serialize(): serialize successful form controls into a query string.serializeArray(): serialize successful form controls into{ name, value }entries.index(): return the first node's index within its parent.indexOf(nodeFilter?): return the first matching index within the set.normalize(): join adjacent text nodes and remove empty text nodes.sort(): sort nodes by document position.tagName(): return the first node's lowercase tag name.
Forms include associated controls outside their DOM subtree. Disabled controls, disabled options, non-submitting controls, and controls inside datalist are excluded. serializeArray() preserves repeated names and original line endings; serialize() normalizes line endings in names and values to CRLF before encoding. Use FormData for file uploads.
Additional static utilities are:
$.debounce(callback)to allow one callback execution per microtask;$.exec(command, value?)to calldocument.execCommand();$.ready(callback)to run after DOM readiness; and$.noConflict()to restore the previous global$.
The active DOM environment can be inspected with $.getWindow() and $.getContext(), or changed with $.setWindow(window) and $.setContext(document). Registering fQuery configures both automatically.
Every export from @fr0st/core is exposed with an underscore prefix:
const id = $._randomString(12);
const values = $._unique([1, 1, 2]);These prefixed helpers follow the installed FrostCore version. Consult FrostCore for its complete API.
- Selector-based operations use the configured document unless an explicit context is supplied.
QuerySetgetter methods generally inspect the first node; mutations generally apply to every node.queryOne()andfindOne...()QuerySet methods return sets containing zero or one node.- HTML strings are recognized by query and creation-aware manipulation APIs when the trimmed string begins with
<. - Custom data, registered events, queues, and animations are tracked outside the DOM and cleaned up by fQuery manipulation methods. Cloning can optionally copy data, events, and animations.
- Event namespaces affect fQuery handler registration and removal; native event dispatch still uses the underlying event name.
- Ajax uses
XMLHttpRequest, notfetch. - fQuery requires a browser DOM or a compatible DOM implementation.
Install dependencies with npm ci, then install Playwright browsers with npx playwright install --with-deps.
npm test
npm run lint
npm run buildnpm test rebuilds the bundles, then runs the Playwright suite in Chromium, Firefox, and WebKit. npm run test:browser runs the suite against the existing bundles, so rebuild after changing source files.
After building, npm run test:coverage runs Chromium tests and writes coverage reports to coverage/.
npm run test:headed and npm run test:ui also use the existing bundles and open headed browsers or the Playwright UI.
fQuery is released under the MIT License.