Selectors

jQuery · Lesson 5 of 24 5 min read Updated

Selecting the right elements is the first step of almost every jQuery statement. The good news: jQuery uses CSS selectors, the same patterns you write in stylesheets. If you know CSS, you already know most jQuery selectors.

In this lesson you will practise the selectors you will use every day, meet the few extra selectors that only jQuery understands, and see how to do the same thing with document.querySelectorAll().

CSS selectors work as they are

Anything you can write in a stylesheet you can pass to $(). Here are the most common patterns, with their plain JavaScript twins:

SelectorMatchesjQueryPlain JavaScript
#cartthe element with id cart$("#cart")document.querySelector("#cart")
.itemevery element with class item$(".item")document.querySelectorAll(".item")
ul lievery <li> inside a <ul> (any depth)$("ul li")document.querySelectorAll("ul li")
ul > li<li> that are direct children$("ul > li")document.querySelectorAll("ul > li")
a[href^="https"]links whose href starts with https$('a[href^="https"]')document.querySelectorAll('a[href^="https"]')
input:checkedticked checkboxes and radios$("input:checked")document.querySelectorAll("input:checked")
li:not(.done)list items without class done$("li:not(.done)")document.querySelectorAll("li:not(.done)")

The one real difference is the return value. $() always returns a jQuery set. querySelector() returns one element or null, and querySelectorAll() returns a static NodeList, which you loop over with forEach.

Try the selectors on a small class list. Each button highlights a different selection:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Selector practice</title>
  <style>
    .pick { background: #ffe58a; }
    li { padding: 0.15rem 0.4rem; }
  </style>
</head>
<body>
  <h2>Class 10-B</h2>
  <ul id="students">
    <li data-city="Rohtak" class="topper">Aarav</li>
    <li data-city="Jaipur">Priya</li>
    <li data-city="Rohtak">Kabir</li>
    <li data-city="Kochi" class="topper">Meera</li>
    <li data-city="Pune">Rohan</li>
  </ul>
  <p>
    <button data-sel=".topper">.topper</button>
    <button data-sel='[data-city="Rohtak"]'>[data-city="Rohtak"]</button>
    <button data-sel="li:not(.topper)">li:not(.topper)</button>
    <button data-sel="li:nth-child(odd)">li:nth-child(odd)</button>
  </p>
  <p id="out">Click a selector.</p>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("button").on("click", function () {
      const selector = $(this).attr("data-sel");
      $("#students li").removeClass("pick");               // clear the last result
      const $found = $("#students").find(selector).addClass("pick");
      $("#out").text(selector + " matched " + $found.length + " student(s)");
    });
  </script>
</body>
</html>

$(this) inside the handler wraps the clicked button, and .find(selector) searches only inside #students, so the buttons themselves are never matched.

Searching inside an element

Searching the whole page is wasteful when you know where to look. Start from a container and use .find():

JavaScript
const $menu = $("#main-menu");
const $links = $menu.find("a");          // only links inside the menu
const $links2 = $("a", "#main-menu");    // same result: a second "context" argument

// Plain JavaScript: any element has querySelectorAll
const menu = document.querySelector("#main-menu");
const links = menu.querySelectorAll("a");

Prefer the .find() form. It reads left to right and makes the starting point obvious.

Selectors that only jQuery understands

jQuery adds some extension selectors that are not part of CSS. You will see them in older code:

jQuery extensionMeaningPlain JavaScript idea
:visible / :hiddentakes up space on the page or notel.checkVisibility() or check offsetParent / getClientRects().length
:contains("text")text includes the given wordsfilter by el.textContent.includes(...)
:inputinput, select, textarea, button"input, select, textarea, button"
:checkbox, :radio, :textinputs of that typeinput[type="checkbox"] and so on
:headerh1 to h6"h1, h2, h3, h4, h5, h6"
:animatedcurrently running a jQuery animationno direct equivalent

These work in jQuery 4, but they have a cost: because the browser does not understand them, jQuery cannot hand the whole selector to the fast built-in querySelectorAll() and must check elements itself. Where you can, select with plain CSS first and then narrow down with a method such as .filter().

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Extension selectors</title>
  <style>.mark { outline: 2px solid #0769ad; }</style>
</head>
<body>
  <ul id="menu">
    <li>Masala dosa</li>
    <li>Paneer dosa</li>
    <li hidden>Cheese dosa (sold out)</li>
    <li>Idli sambar</li>
  </ul>
  <p id="out"></p>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    // jQuery extensions
    const $dosas = $("#menu li:contains('dosa'):visible").addClass("mark");

    // Plain JavaScript: real CSS, then filter the array yourself
    const dosas = [...document.querySelectorAll("#menu li")]
      .filter((li) => li.textContent.includes("dosa") && li.checkVisibility());

    $("#out").text("jQuery found " + $dosas.length + ", plain JS found " + dosas.length + " visible dosas.");
  </script>
</body>
</html>

Both approaches find two dosas: the hidden "Cheese dosa" is skipped. Note that :contains() is case-sensitive, just like includes().

Special characters in ids and classes

Characters such as ., #, : and [ have meaning in selectors. An id like user.name would be read as "id user with class name". Escape it first:

JavaScript
const id = "user.name";
$("#" + $.escapeSelector(id));                       // jQuery 3.0 and later
document.querySelector("#" + CSS.escape(id));        // the browser's built-in version
document.getElementById(id);                         // no escaping needed at all

Common mistakes

  • Forgetting the # or .. $("cart") looks for a <cart> element, not id cart.
  • Searching the whole page repeatedly. Store a container in a variable and call .find() on it.
  • Building selectors from user input without escaping. Unusual characters break the selector or match the wrong elements.
  • Relying on :visible in big loops. It forces the browser to calculate layout. Keep a class such as is-hidden and select that instead.

Key points

  • jQuery accepts any CSS selector; querySelectorAll() accepts the same selectors natively.
  • $() always returns a set; querySelector() can return null.
  • Use .find() on a stored container to search a smaller part of the page.
  • Extension selectors (:visible, :contains(), :input) are jQuery-only and slower; positional ones (:first, :eq()) are deprecated.
  • Escape unusual ids with $.escapeSelector() or CSS.escape().

Exercise

Create a list of five cricket players, each with a data-role attribute of batter, bowler or all-rounder. Add two buttons: the first highlights all bowlers using an attribute selector with jQuery; the second highlights every player who is not a batter using plain querySelectorAll with :not().

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Team roles</title>
  <style>.pick { background: #ffe58a; }</style>
</head>
<body>
  <ul id="team">
    <li data-role="batter">Ananya</li>
    <li data-role="bowler">Farhan</li>
    <li data-role="all-rounder">Isha</li>
    <li data-role="bowler">Rohan</li>
    <li data-role="batter">Kabir</li>
  </ul>
  <button id="bowlers">Bowlers</button>
  <button id="not-batters">Not batters</button>
  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("#bowlers").on("click", function () {
      $("#team li").removeClass("pick");
      $('#team li[data-role="bowler"]').addClass("pick");
    });
    document.querySelector("#not-batters").addEventListener("click", () => {
      const all = document.querySelectorAll("#team li");
      all.forEach((li) => li.classList.remove("pick"));
      document.querySelectorAll('#team li:not([data-role="batter"])')
        .forEach((li) => li.classList.add("pick"));
    });
  </script>
</body>
</html>

The same CSS selector text works in both styles; only the way you act on the result differs.

Quick Quiz

1. Which selector finds only the <li> elements that are direct children of #menu?

2. Why can $("li:visible") be slower than $("li.shown")?

3. What should you write instead of the deprecated $("tr:even")?

4. An element has id="price.total". Which call selects it correctly?

Learn jQuery with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call