Notifications API
Objectives
By the end of this chapter, you should be able to:
- Check and request notification permission
- Show a system notification with
new Notification() - Respond to a user clicking a notification
๐ก Why this matters: A notification shows up outside the browser tab entirely, in the operating system’s own notification area, even if the user has switched to a different app. It’s the browser’s way of reaching a user who isn’t actively looking at the page.
โ ๏ธ A note on verification: the same limitation as the last two lessons applies. Notifications require a real OS-level notification system and a real permission decision, neither exists in this sandbox. The code below is accurate, based on the standardized Notifications API, run it yourself in a browser to see it firsthand.
What You’re Building
A task reminder: type a task, set how many seconds until it’s due, and get an actual system notification, complete with an icon, when the time’s up, even if you’ve switched to another tab by then.
task-reminder/
โโโ index.html
โโโ script.js
โโโ icon.png
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Reminder</title>
</head>
<body>
<h1>Task Reminder</h1>
<input id="taskInput" type="text" placeholder="Task name">
<input id="secondsInput" type="number" placeholder="Remind me in (seconds)">
<button id="enableBtn">Enable Reminders</button>
<button id="scheduleBtn">Schedule Reminder</button>
<p id="reminderStatus"></p>
<script src="script.js"></script>
</body>
</html>
icon.png is a small square image sitting alongside index.html, it never appears directly in the page, the notification itself displays it. Everything else from here on goes in script.js.
Step 1: Check Current Permission
Notification.permission is always one of three strings: "default" (never asked), "granted", or "denied". Check it before doing anything else, no point offering a feature that’s already blocked.
const enableBtn = document.getElementById("enableBtn");
const reminderStatus = document.getElementById("reminderStatus");
if (Notification.permission === "granted") {
reminderStatus.textContent = "Reminders are enabled";
enableBtn.style.display = "none";
} else if (Notification.permission === "denied") {
reminderStatus.textContent = "Reminders are blocked in your browser settings";
enableBtn.style.display = "none";
} else {
reminderStatus.textContent = "Enable reminders to get notified";
}
On a first visit, permission is "default", so the “Enable Reminders” button stays visible and the other two branches don’t apply yet.
Step 2: Request Permission on Click
Notification.requestPermission() shows the browser’s permission prompt, only in response to a real user action, like this button click, never automatically on page load. It’s promise-based.
enableBtn.addEventListener("click", async () => {
const permission = await Notification.requestPermission();
if (permission === "granted") {
reminderStatus.textContent = "Reminders enabled!";
enableBtn.style.display = "none";
} else {
reminderStatus.textContent = "Reminders weren't enabled";
}
});
Step 3: Schedule a Reminder
This is where Module 3’s setTimeout() comes back. “Remind me in 10 seconds” is just a timer that shows a notification when it fires, instead of logging something.
const taskInput = document.getElementById("taskInput");
const secondsInput = document.getElementById("secondsInput");
const scheduleBtn = document.getElementById("scheduleBtn");
scheduleBtn.addEventListener("click", () => {
const taskName = taskInput.value.trim();
const seconds = Number(secondsInput.value);
if (!taskName || !seconds) return;
reminderStatus.textContent = `Reminder set for "${taskName}" in ${seconds}s`;
setTimeout(() => {
if (Notification.permission === "granted") {
new Notification("Task due", {
body: taskName,
icon: "icon.png",
});
}
}, seconds * 1000);
});
icon.png, the file sitting next to index.html, shows up inside the notification itself, in the OS notification tray, next to the title and body text. Notice the Notification.permission === "granted" check happens again here, right before actually showing the notification, not just back in Step 1. Permission could theoretically change between scheduling the reminder and it firing, checking again at the last moment is the safe habit.
Step 4: Respond to a Click
A notification supports .onclick, the same idea as any other event handler. This is where window.focus() earns its keep, bringing the tab back into view if the user was somewhere else when the reminder fired.
setTimeout(() => {
if (Notification.permission === "granted") {
const notification = new Notification("Task due", {
body: taskName,
icon: "icon.png",
});
notification.onclick = () => {
window.focus();
reminderStatus.textContent = `Back to review: ${taskName}`;
notification.close();
};
}
}, seconds * 1000);
Clicking the notification (which might be sitting in the OS notification tray, not the page itself) brings the tab into focus, updates the status text, and closes the notification, .close() dismisses it immediately rather than waiting for it to disappear on its own.
The Complete script.js
const enableBtn = document.getElementById("enableBtn");
const reminderStatus = document.getElementById("reminderStatus");
const taskInput = document.getElementById("taskInput");
const secondsInput = document.getElementById("secondsInput");
const scheduleBtn = document.getElementById("scheduleBtn");
if (Notification.permission === "granted") {
reminderStatus.textContent = "Reminders are enabled";
enableBtn.style.display = "none";
} else if (Notification.permission === "denied") {
reminderStatus.textContent = "Reminders are blocked in your browser settings";
enableBtn.style.display = "none";
} else {
reminderStatus.textContent = "Enable reminders to get notified";
}
enableBtn.addEventListener("click", async () => {
const permission = await Notification.requestPermission();
if (permission === "granted") {
reminderStatus.textContent = "Reminders enabled!";
enableBtn.style.display = "none";
} else {
reminderStatus.textContent = "Reminders weren't enabled";
}
});
scheduleBtn.addEventListener("click", () => {
const taskName = taskInput.value.trim();
const seconds = Number(secondsInput.value);
if (!taskName || !seconds) return;
reminderStatus.textContent = `Reminder set for "${taskName}" in ${seconds}s`;
setTimeout(() => {
if (Notification.permission === "granted") {
const notification = new Notification("Task due", {
body: taskName,
icon: "icon.png",
});
notification.onclick = () => {
window.focus();
reminderStatus.textContent = `Back to review: ${taskName}`;
notification.close();
};
}
}, seconds * 1000);
});
Try It
Build the full task reminder page from this lesson:
- Wire up Step 1 and Step 2, confirm
#reminderStatuscorrectly reflects all three permission states. - Wire up Step 3, scheduling a reminder with
setTimeout()and showing a notification (withicon.png) once permission is granted. - Add the
.onclickhandler from Step 4. - Add a guard so
scheduleBtn’s click handler does nothing (and shows a message asking the user to enable reminders first) ifNotification.permissionisn’t"granted".
Recap
Notification.permissionis"default","granted", or"denied".Notification.requestPermission()asks the user and returns a promise resolving to the same values, this lesson’s “Enable Reminders” button used it directly.new Notification(title, options)shows a notification once permission is granted,options.bodyadds detail text andoptions.iconsets the image shown alongside it..onclickon a notification handles the user clicking it, often paired withwindow.focus()and.close().- Ask for permission in response to a real action, and re-check
Notification.permissionright before showing a notification that was scheduled earlier, not just once upfront.
Next lesson: web workers, running JavaScript on a separate thread so heavy work doesn’t freeze the page.