Selectors & DOM Manipulation

CSS-style $() selectors, reading and writing content and attributes, and traversing the DOM.

$() selectors

jQuery's selector engine accepts the same syntax as CSS, plus a handful of its own extensions:

Javascript
$('#header');          // by ID
$('.card');             // by class
$('p');                 // by tag name
$('ul li');             // descendant selector
$('input[type="email"]'); // by attribute
$('li:first');          // jQuery extension — first matching element
$('li:eq(2)');          // jQuery extension — element at index 2

Every call to $() returns a jQuery object — an array-like wrapper around zero or more matched DOM elements — which is what gives you access to all of jQuery's methods (.text(), .on(), .css(), etc.) on the result.

Javascript
const $items = $('.list-item');
console.log($items.length);   // number of matched elements
console.log($items[0]);       // the raw underlying DOM element, unwrapped

Reading and writing content

Javascript
$('#title').text();              // get the text content
$('#title').text('New Title');   // set the text content

$('#content').html();                        // get the inner HTML
$('#content').html('<strong>Bold</strong>'); // set the inner HTML

$('#email').val();               // get a form field's value
$('#email').val('a@example.com'); // set a form field's value

.text() always treats its argument as plain text (escaping any HTML), while .html() parses its argument as markup — using .text() for user-supplied content and reserving .html() for content you trust is the standard way to avoid accidentally introducing an XSS vulnerability.

Manipulating classes and attributes

Javascript
$('#card').addClass('highlighted');
$('#card').removeClass('highlighted');
$('#card').toggleClass('active');       // adds it if absent, removes it if present
$('#card').hasClass('active');          // true/false

$('#link').attr('href', '/new-path');   // set an attribute
$('#link').attr('href');                // read an attribute
$('#checkbox').prop('checked', true);   // set a boolean DOM property

.attr() and .prop() overlap but aren't identical — .prop() is the correct choice for boolean state (checked, disabled, selected), since those are live DOM properties rather than plain HTML attribute strings; .attr() is right for everything else.

Styling directly

Javascript
$('#box').css('background-color', '#3b82f6');
$('#box').css({
    color: 'white',
    padding: '1rem',
    borderRadius: '8px',
});

Setting styles directly with .css() is convenient for one-off dynamic values, but for anything reusable, toggling a CSS class with .addClass()/.toggleClass() keeps styling in your stylesheet rather than scattered across JavaScript.

DOM traversal

jQuery makes moving around the DOM tree relative to an element straightforward:

Javascript
$('.card').parent();          // the immediate parent element
$('.card').parents('.grid');  // every matching ancestor, however deep
$('.card').closest('.grid');  // the nearest matching ancestor, stopping at the first match
$('.card').children();        // direct children only
$('.card').find('.title');    // any descendant matching the selector, at any depth
$('.card').siblings();        // every sibling element
$('.card').next();            // the immediately following sibling
$('.card').prev();            // the immediately preceding sibling

.find() searches descendants (any depth), while .children() only looks at direct children — a common source of confusion when a nested structure doesn't match at the level expected.

HTML
<div class="grid">
    <div class="card">
        <h3 class="title">Card One</h3>
        <div class="body">
            <span class="title">Nested span, not a heading</span>
        </div>
    </div>
</div>
Javascript
$('.card').find('.title');    // matches BOTH .title elements — any depth
$('.card').children('.title'); // matches NEITHER — .title isn't a direct child of .card

Creating and inserting elements

Javascript
const $newItem = $('<li>').addClass('list-item').text('New item');

$('#list').append($newItem);    // insert as the last child
$('#list').prepend($newItem);   // insert as the first child
$('#list').before($newItem);    // insert before #list, as a sibling
$('#list').after($newItem);     // insert after #list, as a sibling

$('.list-item').remove();       // remove matched elements entirely from the DOM
$('.list-item').empty();        // remove children, but keep the element itself

Chaining

Because most jQuery methods return the same jQuery object they were called on, calls can be chained — a hallmark of jQuery's fluent API style:

Javascript
$('#card')
    .addClass('highlighted')
    .css('opacity', 1)
    .find('.title')
    .text('Updated title');

Each call in the chain operates on the result of the previous one — .find('.title') narrows the selection from #card down to its .title descendant, and the final .text(...) applies to that narrowed selection.

Common mistakes

  • Confusing .find() (descendants at any depth) with .children() (direct children only) and getting an empty or unexpectedly large result set.
  • Using .html() to insert untrusted, user-supplied content — this is a direct cross-site scripting (XSS) risk; use .text() for anything not already sanitized.
  • Re-querying the DOM repeatedly inside a loop ($('.item') called on every iteration) instead of caching the jQuery object once in a variable — this needlessly re-runs the selector engine every time.
  • Using .attr('checked', true) to check a checkbox instead of .prop('checked', true) — the attribute-based approach doesn't reliably reflect the live DOM state.