Detecting when focus leaves a group of elements with vanilla JS
Let’s say you have a group of form inputs, and you want to do something when focus leaves the whole group.
<form action="#">
<label for="input1">Input 1</label>
<input type="text" id="input1" value="Oh">
<label for="input1">Input 2</label>
<input type="text" id="input2" value="hello">
<label for="input1">Input 3</label>
<input type="text" id="input3" value="there">
<button>Submit</button>
</form>
How would you do that? That’s what we’re going to talk about in today’s article. Let’s dig in.
The challenge
Vanilla JS has a focusout
event, so you first inclination might be to use that.
var form = document.querySelector('form');
// Do something when focus leaves the entire form
form.addEventListener('focusout', function (event) {
console.log('focus left!');
});
However, because events bubble, this doesn’t fire when focus leaves the whole form. It fires whenever an element inside the form loses focus.
If you were to tab from #input1
to #input2
, the event listener callback would run. You can see it in action here.
You might also think you can check the event.target
. But that doesn’t work, either, because the event that triggers the event is the field that just lost focus, and not the one that’s receiving it.
Similarly, you can’t check which event currently has focus using the document.activeElement
property, because the event fires at some weird in-between state where the current element has lost focus, but the new one hasn’t received it yet.
// do something when focus leaves the entire form
form.addEventListener('focusout', function (event) {
console.log(event.target, document.activeElement);
});
So… how do you handle this?
The event.relatedTarget
property
Scott Jehl tipped me off to the event.relatedTarget
property.
This is only available on focus events, and returns the “secondary target.” This target varies based on event. On the focusin
event, it’s the item losing focus. On the focusout
event, it’s the item getting focus.
We can check if the event.relatedTarget
in inside the form
element using the contains()
method. If not, the whole group of elements has lost focus.
// do something when focus leaves the entire form
form.addEventListener('focusout', function (event) {
// If focus is still in the form, do nothing
if (form.contains(event.relatedTarget)) return;
// Otherwise, log a message
console.log('Focus left!');
});
Browser compatibility
The event.relatedTarget
property works in all modern browsers, and all the way back to IE9. The Element.contains()
method also works back to IE9, but only for elements, not other node types (like text nodes).