Before you can change anything on a page, you need to find it. JavaScript gives you two main methods — both use CSS selectors, so if you know CSS, you already know how to use them.
Returns the first element that matches a CSS selector:
Copy
// Select by tagconst heading = document.querySelector("h1");// Select by classconst card = document.querySelector(".user-card");// Select by IDconst sidebar = document.querySelector("#sidebar");// Select by attributeconst emailInput = document.querySelector('input[type="email"]');// Select nested elementsconst firstItem = document.querySelector("ul.nav > li");
If no element matches, querySelector returns null — not an error.
Copy
const missing = document.querySelector(".does-not-exist");console.log(missing); // null// ❌ This will crashmissing.textContent = "Hello"; // TypeError: Cannot read properties of null// ✅ Check firstif (missing) { missing.textContent = "Hello";}
querySelector returns null when it can’t find a match. Always check for null if you’re not 100% sure the element exists, or you’ll get TypeError: Cannot read properties of null.
// Get all paragraphsconst paragraphs = document.querySelectorAll("p");console.log(paragraphs.length); // 5// Get all elements with the class "card"const cards = document.querySelectorAll(".card");// Get all checked checkboxesconst checked = document.querySelectorAll('input[type="checkbox"]:checked');
querySelectorAll returns a NodeList, not an array. It has .forEach() but not .map(), .filter(), etc. Spread it into an array with [...nodeList] when you need array methods.
// Form inputsconst nameInput = document.querySelector("#user-form input[name='username']");// Navigation linksconst navLinks = document.querySelectorAll("nav a");// Active tabconst activeTab = document.querySelector(".tab.active");// Data attributes (great for JS hooks)const modal = document.querySelector('[data-modal="settings"]');
Use data-* attributes to select elements meant for JavaScript interaction. This keeps your JS selectors separate from your CSS classes: <button data-action="delete"> instead of <button class="delete-btn">.
// By ID — no # prefix neededconst sidebar = document.getElementById("sidebar");// By class name — returns a live HTMLCollectionconst cards = document.getElementsByClassName("card");// By tag name — returns a live HTMLCollectionconst paragraphs = document.getElementsByTagName("p");
querySelector / querySelectorAll are the modern standard. They’re more flexible (any CSS selector) and more consistent (querySelectorAll always returns a static NodeList).
You can call querySelector on any element, not just document:
Copy
const form = document.querySelector("#user-form");// Search only inside the formconst nameInput = form.querySelector('input[name="username"]');const submitBtn = form.querySelector('button[type="submit"]');
This is useful when you have multiple similar structures on the page and need to target elements within a specific container.
<!-- ❌ Script runs before the body is parsed --><head> <script src="app.js"></script></head><body> <h1>Hello</h1></body>
Copy
// app.js — h1 doesn't exist yet when this runs!const heading = document.querySelector("h1"); // null
Copy
<!-- ✅ Use defer — script runs after DOM is ready --><head> <script defer src="app.js"></script></head><body> <h1>Hello</h1></body>
Scripts in <head> run before <body> is parsed. Use defer on your script tag, or place the <script> at the bottom of <body>. The defer approach is modern and preferred.
Forgetting the dot or hash in selectors
Copy
// ❌ Wrong — missing . for classconst card = document.querySelector("card"); // Looks for <card> element// ✅ Correctconst card = document.querySelector(".card"); // Looks for class="card"// ❌ Wrong — missing # for IDconst sidebar = document.querySelector("sidebar"); // Looks for <sidebar> element// ✅ Correctconst sidebar = document.querySelector("#sidebar"); // Looks for id="sidebar"
Treating querySelectorAll like an array
Copy
const items = document.querySelectorAll(".item");// ❌ Won't work — NodeList doesn't have .map()const names = items.map(item => item.textContent);// ✅ Spread into an array firstconst names = [...items].map(item => item.textContent);