jQuery / Selector / :has() - Select Elements Containing a Matching Element
jQuery's :has(selector) selects elements containing at least one matching descendant. $("#menu > li:has(a)") returns li elements containing links, not the inner a elements. Descendants include direct children and deeper nested elements, so distinguish the condition when only immediate children should count.
The containing element versus the contained element
The :has() documentation describes retaining candidate elements with a matching descendant. A link inside a span is still a descendant of its li and satisfies li:has(a). A li whose class happens to be has-link does not satisfy the condition unless an actual a element is inside it.
| Expression | Condition checked | Returned elements |
|---|---|---|
| li:has(a) | An a exists at any depth inside | Matching outer li elements |
| li:has(> a) | An a is an immediate child | Matching outer li elements |
| Select li, then .has("a") | Selected li contains a descendant a | A subset of the original li set |
| Select li, then .find("a") | Find a elements inside the li | The inner a elements |
> denotes the direct-child relationship. The outer #menu > li determines which items are candidates, while > a inside :has() defines the relationship to test within each item. Reading these roles separately helps avoid unintended matches in nested structures.
Complete example highlighting items with links
Save the entire code as UTF-8 in has-demo.html and open it in a browser. No package installation is needed, but an internet connection is required to load the official jQuery distribution from its Content Delivery Network (CDN). The example uses 4.0.0; the same behavior was executed and checked with both 3.7.1 and 4.0.0.
plain has no link, direct has an immediate a child, nested has an a inside span, and self has only the class name has-link. The buttons compare selection conditions with a document change.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Items that contain links</title>
<style>
body { font: 16px/1.6 sans-serif; margin: 24px; }
#menu { list-style: none; padding: 0; max-width: 600px; }
#menu > li { border: 2px solid #ddd; margin: 8px 0; padding: 12px; }
#menu > li.marked { border-color: #c45f00; background: #fff5e7; }
button { margin: 4px; padding: 8px; }
</style>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
</head>
<body>
<h1>Items that contain links</h1>
<ul id="menu">
<li id="plain">No link</li>
<li id="direct"><a id="link-1" href="#example">Direct link</a></li>
<li id="nested"><span><a id="link-2" href="#example">Nested link</a></span></li>
<li id="self" class="has-link">Class name only</li>
</ul>
<button type="button" id="descendants">Any descendant link</button>
<button type="button" id="children">Direct child link</button>
<button type="button" id="add">Add a link to plain</button>
<button type="button" id="reset">Reset</button>
<p id="result" aria-live="polite"></p>
<script>
$(function () {
function show($chosen) {
$("#menu > li").removeClass("marked");
$chosen.addClass("marked");
const ids = $chosen.map((_, item) => item.id).get().join(", ");
$("#result").text(`${ids || "none"} | ${$chosen.length}`);
}
function refresh() {
show($("#menu > li:has(a)"));
}
$("#descendants").on("click", refresh);
$("#children").on("click", () => show($("#menu > li:has(> a)")));
$("#add").on("click", function () {
if (!$("#added-link").length) {
$("#plain").append('<a id="added-link" href="#example"> Added link</a>');
}
refresh();
});
$("#reset").on("click", function () {
$("#added-link").remove();
refresh();
});
refresh();
});
</script>
</body>
</html>
Initially, the outer borders of direct and nested are highlighted and the result displays:
direct, nested | 2
Check direct children, dynamic changes and reset
- Any descendant link accepts links at any depth and shows
direct, nested | 2. - Direct child link checks only immediate a children and shows
direct | 1. nested is excluded because its link is under span. - Add a link to plain adds a link and produces
plain, direct, nested | 3. Pressing it again checks the added link's ID instead of creating duplicates. - Reset removes only the demonstration's added link and reapplies the default descendant condition. The original No link text and items remain, restoring the initial two matches.
show clears the old highlights before marking the new result. refresh queries the current document again. This prevents both relying on a stale selection after insertion and leaving highlights on items that no longer satisfy the chosen condition.
Compare .has() with .find()
When you already have a set of li elements, the .has() method can retain only the required candidates. Open the example or press Reset, then run this partial code in the developer Console:
const ids = $set => $set.map((_, item) => item.id).get().join(", ");
console.log(ids($("#menu > li:has(a)")));
console.log(ids($("#menu > li").has("a")));
console.log(ids($("#menu > li").find("a")));
direct, nested
direct, nested
link-1, link-2
The first two results are outer li IDs; .find() returns inner a IDs. .has() filters an existing set by descendant conditions rather than climbing upward to discover parents. It also differs from .filter(), which tests the candidate elements themselves.
.has() accepts a selector string or a specific Document Object Model (DOM) element. With an element argument, it still retains candidates containing that element as a descendant. If only the candidate itself matches, or no descendant matches, the result is empty.
Scope, re-querying and CSS
A broad $(":has(a)") can include multiple ancestors containing links. Narrow the candidate container and tag to your actual goal. A jQuery selection does not update automatically; run it again after adding or removing descendants to reflect the current structure.
Selectors Level 4 also defines CSS :has(), so describing the notation as universally exclusive to jQuery would be inaccurate. This article uses the jQuery API to obtain element sets. When comparing it with CSS rules that only change styling, check your project's supported browsers and the selector conditions you need.
If the example does not respond, inspect Network for the jQuery request and Console for errors. Check parentheses, quotes, actual tags and nesting depth. Selection depends on real a elements, not the visible word “link” or a class name.




