Focusing: focus/blur
Click into a text field and it lights up, ready for typing. Tab away and it dims. That handoff of attention from one control to another is focus, and the browser fires events at both ends of it. Learn to read those events and you can validate a field the moment a user leaves it, initialize a widget the instant it wakes up, or manage keyboard navigation for a custom UI.
An element takes focus when the user clicks it or reaches it with the Tab key. HTML also gives you the autofocus attribute, which hands focus to one element as the page loads, and there are other routes in too (we’ll get to tabindex and the focus() method below).
Getting focus roughly means “get ready to accept input here.” That’s your cue to run setup code: open a dropdown, show a hint, clear a stale error.
Losing focus, called blur, is often the more useful moment. It fires when the user clicks elsewhere, presses Tab to advance to the next field, or leaves in some other way.
Losing focus roughly means “the user is done with this field.” That’s a natural point to validate what they typed, or push the value to the server.
Focus events carry some sharp edges that trip people up. The rest of this lesson walks through them.
Events focus/blur
The focus event fires when an element gains focus; blur fires when it loses focus. Put them to work validating a hex-color field.
In the example below:
- The
blurhandler checks whether the field holds a hex color, and if not, shows an error. - The
focushandler clears that error, since the user is coming back to fix it (blur will re-check on the way out).
<style>
.invalid { border-color: red; }
#error { color: red }
</style>
Pick a hex color: <input type="text" id="input">
<div id="error"></div>
<script>
input.onblur = function() {
if (!input.value.startsWith('#')) { // not a hex color
input.classList.add('invalid');
error.innerHTML = 'Please enter a hex color (start with #).'
}
};
input.onfocus = function() {
if (this.classList.contains('invalid')) {
// clear the error marking, the user wants another try
this.classList.remove('invalid');
error.innerHTML = "";
}
};
</script>
The pairing is the point: validate on the way out, forgive on the way back in. Checking on blur means you don’t nag the user mid-keystroke while they’re still typing. Try it: type something without a #, then click away to trigger the check, then click back in to clear it.
Methods focus/blur
The methods elem.focus() and elem.blur() set and remove focus programmatically. That lets you drive focus from code instead of waiting for the user.
Here’s a stricter version of the hex-color check that refuses to let the user leave an invalid field:
<style>
.error {
background: red;
}
</style>
Pick a hex color: <input type="text" id="input">
<input type="text" style="width:220px" placeholder="make color invalid and try to focus here">
<script>
input.onblur = function() {
if (!this.value.startsWith('#')) { // not a hex color
// show the error
this.classList.add("error");
// ...and yank focus back
input.focus();
} else {
this.classList.remove("error");
}
};
</script>
Enter something that isn’t a hex color, then try to Tab or click away. The onblur handler runs, sees the bad value, and calls input.focus() to drag focus back to the field.
This works in every browser except Firefox, where a long-standing bug prevents it.
Focus loss you didn’t ask for
A blur can fire for reasons that have nothing to do with the user tabbing away, and those surprise firings are a common source of buggy handlers.
→ alert opens: blur on input
→ alert dismissed: focus back on input
→ input removed: blur fires
→ re-inserted later: focus does not return
Two cases to watch:
- An
alertmoves focus to itself. So opening one firesbluron your element, and dismissing it firesfocusas control returns. - Removing an element from the DOM also drops its focus and fires
blur. If you reinsert it later, focus does not come back on its own.
When these happen, focus/blur handlers can run at moments you never intended. The defense is simple to state: if you want to track user-initiated focus loss, don’t cause focus loss yourself in the middle of it.
Allow focusing on any element: tabindex
Not every element can be focused. The exact list shifts between browsers, but one rule always holds: anything a user interacts with, like <button>, <input>, <select>, and <a>, supports focus and fires focus/blur.
Elements that exist purely to structure or format content, like <div>, <span>, and <table>, are unfocusable by default. Calling elem.focus() on them does nothing, and their focus/blur events never fire.
The tabindex attribute changes that. Add it and any element becomes focusable. Its value sets the element’s position in the Tab order.
So with two elements where the first has tabindex="1" and the second tabindex="2", pressing Tab from the first moves focus to the second.
The traversal order has two tiers. First come all elements with a positive tabindex, visited in ascending numeric order (1, then 2, then 3…). After those are exhausted, the browser moves through elements without a positive tabindex, in document source order.
Two values are special:
tabindex="0"makes an element focusable but keeps it in the normal document-order group. It gets tabbed to after every positive-tabindexelement, alongside ordinary controls. This is the common choice: focusable, on par with a real<input>, and no reshuffling of order.tabindex="-1"allows only programmatic focus. The Tab key skips the element entirely, butelem.focus()still works. Handy for elements you want to focus from code (say, a dialog you just opened) without inserting them into keyboard navigation.
Here’s a list that mixes all four cases. Click the first item, then press Tab and watch where focus lands:
Click the first item and press Tab. Track the order. Note that
many Tabs in a row can push focus out of the example frame.
<ul>
<li tabindex="1">Espresso</li>
<li tabindex="0">Latte</li>
<li tabindex="2">Mocha</li>
<li tabindex="-1">Cortado</li>
</ul>
<style>
li { cursor: pointer; }
:focus { outline: 1px dashed green; }
</style>
Focus visits them in the order 1 → 2 → 0. “Cortado” never gets a turn from the keyboard. A plain <li> can’t normally be focused at all, but tabindex switches on focusing, the focus/blur events, and the ability to style it with :focus.
Try it live below. Click “Espresso”, then press Tab and each item records the moment it received focus, building up the real traversal order:
Delegation: focusin/focusout
Here’s the quirk that catches everyone: focus and blur do not bubble.
Most events travel up the tree, so a handler on a parent hears about events on its children. Focus events don’t. That breaks the obvious approach of highlighting a whole form when any field inside it gains focus:
<!-- try to add a class when focus lands inside the form -->
<form onfocus="this.className='focused'">
<input type="text" name="title" value="Title">
<input type="text" name="author" value="Author">
</form>
<style> .focused { outline: 1px solid red; } </style>
This does nothing. Focusing an <input> fires focus on that input alone. The event doesn’t rise to the <form>, so form.onfocus never runs.
There are two ways around it.
Option 1: catch it on the capturing phase
A historical wrinkle: focus/blur don’t bubble up, but they do propagate downward on the capturing phase. So a listener registered with the capture flag on the <form> sees focus events headed for its children.
<form id="form">
<input type="text" name="title" value="Title">
<input type="text" name="author" value="Author">
</form>
<style> .focused { outline: 1px solid red; } </style>
<script>
// the third argument, true, means capturing phase
form.addEventListener("focus", () => form.classList.add('focused'), true);
form.addEventListener("blur", () => form.classList.remove('focused'), true);
</script>
Option 2: use focusin/focusout
There’s a second pair of events, focusin and focusout, that behave exactly like focus/blur but do bubble. That makes delegation straightforward.
One catch: these must be attached with elem.addEventListener, not the on<event> property. There is no working elem.onfocusin.
<form id="form">
<input type="text" name="title" value="Title">
<input type="text" name="author" value="Author">
</form>
<style> .focused { outline: 1px solid red; } </style>
<script>
form.addEventListener("focusin", () => form.classList.add('focused'));
form.addEventListener("focusout", () => form.classList.remove('focused'));
</script>
Both approaches reach the same goal. Capturing works with the original events and needs the true flag; focusin/focusout read more naturally but must go through addEventListener. Pick whichever fits the surrounding code.
Here’s the focusin/focusout version running. Click between the two fields, then click outside the form entirely, and watch the whole form light up and dim:
Finding the focused element
At any moment, the element that currently holds focus is available as document.activeElement. When nothing is focused, it points at document.body (or null in some edge cases). It’s useful for questions like “which field was the user in when they hit this shortcut?”
Move focus around the controls below (click or Tab) and the readout reports whatever document.activeElement points at right now:
Summary
The focus and blur events fire when an element gains or loses focus. Their peculiarities:
- They don’t bubble. To handle focus changes on a parent, either listen on the capturing phase (
addEventListenerwithtrue), or use the bubblingfocusin/focusoutevents (which requireaddEventListener, noton<event>). - Most elements aren’t focusable by default. Add
tabindexto make any element focusable and part of keyboard navigation. Usetabindex="0"to keep normal order andtabindex="-1"for script-only focus; avoid positive values. - Blur can’t be prevented with
preventDefault, because it runs after focus has already left. Re-focus withelem.focus()if you truly must. - Focus can shift for reasons you didn’t trigger (an
alert, a removed DOM node), so guard handlers against firing at unwanted moments.
The currently focused element is always readable as document.activeElement.