Write Your Own jQuery Selectors like :external
Buried in jQuery's guts is an extension point most people never find: the selector engine itself is pluggable. Those pseudo-selectors like :visible and :checked? You can add your own, and they become first-class citizens everywhere jQuery accepts a selector.
$.expr.pseudos.external = function (el) {
return el.hostname && el.hostname !== location.hostname;
};
That's a complete custom selector. Now, anywhere in the codebase:
$('a:external').attr({ target: '_blank', rel: 'noopener' });
$('#content').on('click', 'a:external', trackOutboundClick); // works in delegation too
if ($(link).is(':external')) { ... } // and in .is(), .filter(), .not()
The ones I've actually shipped
// Inputs that are empty or whitespace-only
$.expr.pseudos.blank = el =>
/^(input|textarea)$/i.test(el.tagName) && !el.value.trim();
// Elements currently outside the viewport
$.expr.pseudos.offscreen = (el) => {
const r = el.getBoundingClientRect();
return r.bottom < 0 || r.top > innerHeight || r.right < 0 || r.left > innerWidth;
};
// Elements with any data-* attributes (auditing legacy pages)
$.expr.pseudos.hasdata = el =>
Object.keys(el.dataset).length > 0;
Form validation reads like prose afterwards: $('#signup :blank').addClass('error'). The audit selector pairs beautifully with the console: $(':hasdata') in DevTools on an unfamiliar page tells you where the JavaScript hooks are.
Parameterized selectors
Selectors can take arguments via createPseudo:
$.expr.pseudos.attrStartsWith = $.expr.createPseudo(function (prefix) {
return function (el) {
return [...el.attributes].some(a => a.name.startsWith(prefix));
};
});
$('div:attrStartsWith(data-widget)'); // any div with data-widget-* attributes
The performance honesty section
Custom pseudos can't use querySelectorAll - jQuery must fetch candidates and run your function against each one. That's fine for hundreds of elements and event delegation checks; it's not fine as $(':offscreen') against a 10,000-node document on every scroll. Two rules keep you safe: qualify the selector (a:external, not :external) so the candidate set is small, and keep the test function cheap - it runs per element.
Is this worth it in 2026? If you maintain a jQuery codebase (statistically, many of us quietly do), absolutely - it concentrates gnarly conditions into named, testable, reusable predicates. And the underlying lesson transfers: modern equivalents are small predicate functions with array filter, or the :has() selector, which brought this kind of expressiveness natively to CSS.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.