Question
How can I run one or more standard JavaScript functions as soon as the HTML document is ready to be accessed and modified, without using jQuery's $(document).ready()?
For example, jQuery provides:
$(document).ready(function () {
// Code that needs the DOM goes here.
});
What is the appropriate cross-browser approach in vanilla JavaScript? How does it differ from window.onload, an inline onload attribute on <body>, or calling a function from a script placed near the end of the page?
Short Answer
The vanilla JavaScript equivalent of jQuery's document-ready callback is usually the DOMContentLoaded event. It fires when the browser has parsed the HTML and built the DOM, so JavaScript can safely find and update page elements. You will also learn when to use load, how defer can remove the need for a ready listener, and how to handle code that may run after the DOM is already ready.
Concept
$(document).ready(...) is a jQuery convenience for waiting until the DOM is ready.
In modern browser JavaScript, listen for the DOMContentLoaded event:
document.addEventListener("DOMContentLoaded", function () {
myFunction();
});
The DOM (Document Object Model) is the browser's JavaScript representation of the HTML page. Before the browser has parsed an element, code such as this may fail because the element does not exist yet:
const button = document.querySelector("#save-button");
DOMContentLoaded means:
- The HTML has been parsed.
- DOM elements are available to query and modify.
- Deferred scripts have run.
- Images, stylesheets, fonts, videos, and other external resources may still be loading.
This distinction matters because many page-initialization tasks need elements to exist, but do not need every image or external resource to finish downloading.
For nearly all supported modern browsers, DOMContentLoaded is the correct replacement for a jQuery ready callback.
Mental Model
Think of a web page as a building being assembled.
- HTML parsing is the construction crew placing rooms, doors, and furniture into the building plan.
- The DOM is the completed floor plan that JavaScript can inspect and change.
DOMContentLoadedis the announcement: “The building layout is complete; you can start arranging things.”window.loadis the later announcement: “Everything has arrived, including paintings, appliances, and decorations.”
If your code only needs to find a button and attach a click handler, wait for the layout (DOMContentLoaded), not every decorative resource (load).
Syntax and Examples
Use addEventListener to register a callback for DOMContentLoaded:
document.addEventListener("DOMContentLoaded", function () {
console.log("The DOM is ready.");
});
An arrow function works too:
document.addEventListener("DOMContentLoaded", () => {
const message = document.querySelector("#message");
message.textContent = "JavaScript started after the DOM was ready.";
});
Given this HTML:
<p id="message">Loading...</p>
the callback can safely select #message because the browser has finished parsing the document before it runs.
A reusable whenDomReady helper
Step by Step Execution
Consider this page:
<!doctype html>
<html lang="en">
<head>
<script>
document.addEventListener("DOMContentLoaded", () => {
const status = document.querySelector("#status");
status.textContent = "Ready";
});
</script>
</head>
<body>
<p id="status">Waiting...</p>
</body>
</html>
Execution sequence:
- The browser starts parsing the HTML.
- It reaches the
<script>in<head>. - The script registers a
DOMContentLoadedlistener, then finishes. It does try to select yet.
Real World Use Cases
Use DOM-ready logic when your code depends on page elements being present:
- Form behavior: attach
submit,input, orchangelisteners after locating a form. - Navigation menus: initialize a mobile-menu button and its panel.
- Client-side validation: find required fields and show validation messages.
- Dashboard widgets: populate tables, charts, counters, or filters after their containers exist.
- Accessibility enhancements: set focus, update ARIA attributes, or add keyboard handlers.
- Progressive enhancement: turn server-rendered HTML into tabs, accordions, modals, or interactive components.
Use the load event instead when the actual dimensions or full availability of resources are required. For example, an image-processing script may need an image to finish loading before reading its natural size.
Real Codebase Usage
In current projects, developers often avoid a global “ready” wrapper by loading scripts with defer:
<script src="app.js" defer></script>
A deferred external script runs after HTML parsing is complete, so app.js can usually access DOM elements directly:
const menuButton = document.querySelector("#menu-button");
const menu = document.querySelector("#menu");
menuButton.addEventListener("click", () => {
menu.hidden = !menu.hidden;
});
Common project patterns include:
-
Component initialization: find all matching elements and initialize each one.
document.querySelectorAll("[data-tabs]").forEach(() => { (tabsElement); });
Common Mistakes
Selecting elements too early
This script is in <head>, but the button appears later in <body>:
const button = document.querySelector("#save-button");
button.addEventListener("click", save);
button may be null, causing an error. Fix it by using DOMContentLoaded, adding defer to the external script, or placing a non-deferred script after the relevant HTML.
Using window.onload for DOM-only work
window.addEventListener("load", initializeMenu);
This works, but it waits for images and other resources too. For normal DOM setup, prefer:
document.addEventListener("DOMContentLoaded", initializeMenu);
Overwriting another handler
Comparisons
| Approach | Runs when | Best use | Notes |
|---|---|---|---|
document.addEventListener("DOMContentLoaded", fn) | The HTML has been parsed into a DOM | Initializing elements and event handlers | Standard replacement for jQuery ready |
<script defer src="app.js"></script> | After HTML parsing, before DOMContentLoaded completes | Loading application scripts | Often removes the need for a ready listener |
Script at the end of <body> | After the HTML before that script has been parsed | Small pages or simple scripts | Works, but defer often keeps HTML cleaner |
window.addEventListener("load", fn) | The full page and dependent resources are loaded |
Cheat Sheet
// Standard DOM-ready listener
document.addEventListener("DOMContentLoaded", initialize);
function initialize() {
// Query and modify DOM elements here.
}
<!-- Preferred way to load an external app script -->
<script src="app.js" defer></script>
// Safe even if the DOM may already be ready
function whenDomReady(callback) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", callback, { once: true });
} else {
callback();
}
}
// Use only when all page resources must be finished
window.addEventListener(, {
});
FAQ
What is the vanilla JavaScript equivalent of $(document).ready()?
Use document.addEventListener("DOMContentLoaded", callback). It runs the callback after the HTML has been parsed into a DOM.
Is DOMContentLoaded the same as window.onload?
No. DOMContentLoaded occurs after HTML parsing. load occurs later, after dependent resources such as images have loaded.
Do I need DOMContentLoaded when my script uses defer?
Usually no. A deferred external script runs after document parsing, so DOM elements are available. You may still use a listener if it makes the initialization timing clearer.
Can I put a script at the bottom of the body instead?
Yes. A script after the elements it uses can access those elements. However, an external script with defer is often easier to organize and keeps script-loading behavior explicit.
Why does my DOMContentLoaded callback not run?
It may have been registered after the event already fired. Check document.readyState; if it is not "loading", call the initialization function immediately.
Can more than one handler be added?
Mini Project
Description
Build a small notification banner that is initialized only after its HTML exists. The banner can be dismissed with a button, demonstrating safe DOM selection, event listeners, and a reusable DOM-ready helper.
Goal
Create a dismissible page notification that works whether the initialization script runs before or after the DOM becomes ready.
Requirements
Use a whenDomReady function that checks document.readyState.
Select the notification and its dismiss button after the DOM is ready.
Hide the notification when the user clicks the dismiss button.
Do not use inline onclick or onload attributes.
Avoid errors if the notification is not present on a page.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.