jQuery :has() Selector — Match Parents With Descendants

Beginner
7 min read
Updated: Oct 2026
5 examples
Live preview

What Is the :has() Selector?

:has(selector) (since jQuery 1.1.4) selects elements that contain at least one descendant matching selector — at any nesting depth, not only direct children.

Remember
$("div:has(p)");           // divs with a <p> inside
$("li:has(ul)");           // list items with a nested list
$("article:has(.error)");  // articles that hold an error

// Preferred today:
$("div").has("p");         // same idea, faster path
NeedUse
Parents with a matching descendant$("el").has("sel") · legacy $("el:has(sel)")
The descendants themselves.find()
Match by text content:contains()
Direct children only$("el > child") · .children()
Exclude matches:not()

How to Use :has()

Pick containers based on what they hold — then prefer the .has() method in new code.

  1. Choose the outer elements to test ($("div"), $("li"), …).
  2. Decide which descendant must exist (p, ul, .error, …).
  3. Legacy: append :has(selector) · Modern: call .has(selector).
  4. Remember: any depth counts — not only direct children.
  5. Style or act on the matching containers (not the inner matches).
PieceRequired?Role
Preceding selectorYesOuter candidates to keep or drop
Inner selectorYesDescendant that must exist somewhere inside
.has() methodRecommendedSame filter; better with pure CSS base selects

Pseudocode

Logic
for each element E in $("outer")
  if E contains at least one descendant matching "inner"
    keep E
  else
    drop E

// Prefer:
$("outer").has("inner")

Cheat Sheet

GoalCode
Divs with a paragraph$("div").has("p")
Legacy selector string$("div:has(p)")
List items with nested lists$("li").has("ul")
Cards with an error$(".card").has(".error")
Get the paragraphs instead$("div").find("p")
Match by text$("div:contains(\"Hello\")")

Live Preview

Highlight containers that contain a matching descendant — compare :has() and .has().

Hello in a paragraph

Hello again! (with no paragraph)
Only a span here

Matched boxes get a red inset border. Box A has a p; box C has a span.

Result
Ready — click Run to filter boxes.

jQuery Examples

Short, copy-ready snippets. Open DevTools, or use Try It Yourself for a live editor.

Example 1 — Official div:has(p)

From the jQuery docs: add class test to divs that contain a paragraph.

jQuery
$("div:has(p)").addClass("test");
Try It Yourself

How It Works

  1. Candidates. Every div is tested.
  2. has(p). Keep only those with a p somewhere inside.
  3. Depth. Nested paragraphs still count.
  4. Class. test marks the matching containers.

Example 2 — Modern .has() Method

Same result as :has(p) — the recommended performance pattern.

jQuery
$("div").has("p").addClass("test");
// Same matches as $("div:has(p)")
Try It Yourself

How It Works

  1. CSS first. $("div") can use querySelectorAll.
  2. Filter. .has("p") keeps containers with a p.
  3. Same idea. Still returns parents, not the paragraphs.
  4. Docs tip. Official API recommends this over the pseudo-class.

Example 3 — Nested Lists li:has(ul)

Bold list items that contain a nested sublist.

jQuery
$("li:has(ul)").css("fontWeight", "bold");
// Modern: $("li").has("ul").css("fontWeight", "bold");
Try It Yourself

How It Works

  1. Set. All li elements are candidates.
  2. has(ul). Keep only those wrapping a ul.
  3. Leaves. Items without a sublist are skipped.
  4. Style. Bold marks parents with children menus.

Example 4 — :has() vs :contains()

Structure filter versus text substring — different matching rules.

jQuery
console.log(":has(p) →", $("div:has(p)").length);
console.log(":contains(Hello) →", $("div:contains('Hello')").length);
Try It Yourself

How It Works

  1. :has(p). Needs a real p element inside.
  2. :contains. Needs the text “Hello” anywhere in the div.
  3. Overlap. A div can match one, both, or neither.
  4. Pick. Use structure for tags; text for words.

Example 5 — Highlight Parents With Errors

Flag form groups that contain an .error message.

jQuery
$(".field").has(".error").css({
  border: "2px solid #dc2626",
  padding: "8px"
});
Try It Yourself

How It Works

  1. Scope. Start from .field containers.
  2. has(.error). Keep fields that hold an error node.
  3. Style. Draw attention to the whole group.
  4. Pattern. Same idea for cards, rows, or sections.

Edge Cases & Pitfalls

CaseWhat happensFix
Expecting only direct childrenDeep descendants also matchUse > / .children() when depth must be 1
Wanting the inner elements:has returns parentsUse .find()
Matching by textWrong toolUse :contains()
Putting :has in a long selectorSlower — no native QSA pathUse $("css").has("sel")
Bare $(":has(p)")Scans too broadlyAlways prefix: $("div:has(p)")

Browser Support

:has() has been part of jQuery since 1.1.4. It works wherever jQuery runs, but as a jQuery extension it skips the native querySelectorAll fast path — prefer .has() in new code.

✓ jQuery 1.1.4+

jQuery :has()

Supported across jQuery 1.x–3.x where the library is loaded. For modern apps, use $("sel").has("inner") instead of embedding :has() in the selector string.

Stable Status
Google Chrome All versions · Desktop & Mobile
Full support
Mozilla Firefox All versions · Desktop & Mobile
Full support
Apple Safari All versions · macOS & iOS
Full support
Microsoft Edge All versions · Chromium & Legacy
Full support
Internet Explorer IE 6+ · Legacy environments
Full support
Opera All modern versions
Full support
:has() Since 1.1.4

Bottom line: Safe to use for learning and legacy code. Prefer .has() for performance on modern browsers.

Performance Notes

ApproachCostBest for
$("css").has("sel")BestModern browsers — official recommendation
$("el:has(sel)")SlowerShort demos / legacy tutorials
Huge DOM + deep treesCan add upNarrow the base set first

Because :has() is a jQuery extension, the full query cannot ride native querySelectorAll. Select with CSS, then filter with .has().

Key Takeaways

  • Role: keep parents that contain a matching descendant — since 1.1.4.
  • Prefer: $("el").has("sel") over $("el:has(sel)").
  • Depth: any descendant counts — not only direct children.
  • Not text: for words use :contains(); for inner nodes use :has() / .has().

One line: :has(sel) keeps elements that contain sel — prefer .has(sel) in modern jQuery.

Frequently Asked Questions

:has(selector) selects elements that contain at least one descendant matching the inner selector. $("div:has(p)") matches every div with a p anywhere inside — not only as a direct child. Available since jQuery 1.1.4.
No. Official docs: the match can exist anywhere among descendants — grandchildren and deeper count. Use the child combinator (>) when you need direct children only.
Official jQuery docs recommend $("div").has("p") instead of $("div:has(p)") for better performance in modern browsers. Separate a pure CSS base selector from the descendant filter.
They look similar. jQuery :has() is a jQuery extension since 1.1.4 and does not use native querySelectorAll the same way. Modern CSS also defines :has(), but in jQuery code prefer .has() on a CSS-selected collection.
:has() filters by descendant elements matching a selector — structure. :contains() filters by visible text substring — content. div:has(p) looks for a p tag; div:contains("Hello") looks for text.
.find() returns the matching descendants. :has() / .has() return the container elements that hold those descendants. $("div").find("p") gives paragraphs; $("div").has("p") gives the divs.

Next: :header Selector

Select heading elements (h1–h6) in one go.

:header tutorial →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

10 people found this page helpful