A modern, context-aware, and extendable CSS selector engine built on top of
querySelectorAll
.
Download the CJS, ESM, UMD versions or install via NPM:
npm install @ryanmorr/sonic
Find a single element:
import { find } from '@ryanmorr/sonic';
// Returns the matching element or null if no match is found
const element = find('#container');
Query for multiple elements:
import { query } from '@ryanmorr/sonic';
// Returns an array of all matching elements
const elements = query('.items');
Check if an element matches a selector string:
import { is } from '@ryanmorr/sonic';
const isMatch = is(element, 'div.class[attr=value]');
Provide an element or selector string as an optional second argument as the root of the query:
const element = find('[attr]', element);
const elements = query(':first-child', '#header');
Use leading combinators:
const divs = query('> div');
const blocks = query('+ .block');
const checked = query('~ :checked');
Extend by creating custom pseudo-class selectors (must return a boolean):
import { find, query, pseudos } from '@ryanmorr/sonic';
pseudos.foo = (element) => {
return element.hasAttribute('foo');
};
pseudos.bar = (element, value) => {
return element.hasAttribute(value);
};
const element = find(':foo');
const elements = query(':bar(class)');
Sonic addresses the long-standing flaw in querySelector
and querySelectorAll
that sees element-rooted queries search relative to the document and not the element itself:
<section id="container">
<em>Level 1</em>
<section>
<em>Level 2</em>
</section>
</section>
// Expected <em>Level 2</em>, but returns <em>Level 1</em>, doh!
document.querySelector('#container').querySelector('section em');
Apparently, this behavior is purported to be correct given how long it has endured. Sonic, on the other hand, abides by the principle of least surprise and gives you exactly what you expect.
// Returns <em>Level 2</em> as expected, hooray!
const elements = query('section em', '#container');
This project is dedicated to the public domain as described by the Unlicense.