<div id="root">
</div>
<div class="blog-note">
Check the blog post <a href="https://muffinman.io/blog/catching-the-blur-event-on-an-element-and-its-children/" target="_parent">here</a>.
</div>
$blue: #4285f4;
@mixin md {
@media (min-width: 500px) {
@content;
}
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Helvetica, Arial, sans-serif;
}
.container {
max-width: 500px;
margin: 0 auto;
padding: 40px 20px;
@include md {
display: flex;
justify-content: space-between;
}
}
.column {
flex-basis: 25%;
align-self: center;
}
.group {
border: 1px solid #eee;
border-radius: 4px;
padding: 15px;
display: flex;
flex-direction: column;
margin: 20px 0;
flex-basis: 50%;
font-size: 15px;
color: #ddd;
@include md {
margin: 0 20px;
align-items: center;
}
button {
margin-bottom: 10px;
}
}
.group--focused {
border-color: $blue;
box-shadow: 0 0 0 2px $blue;
color: #111;
}
button {
display: block;
width: 100%;
border-radius: 100px;
border: none;
outline: none;
line-height: 40px;
max-width: 100px;
background: #f1f2f6;
&:hover {
cursor: pointer;
box-shadow: 0 0 0 3px #e1e2ea;
}
&:focus {
box-shadow: 0 0 0 3px $blue;
}
}
.blog-note {
color: #aaa;
text-align: center;
padding: 60px 20px 40px;
a {
color: #aaa;
}
a:hover,
a:focus {
color: $blue;
}
}
View Compiled
const { useCallback, useState } = React;
// ----- The main component
const ChildrenBlur = ({ children, onBlur, ...props }) => {
const handleBlur = useCallback(
(e) => {
const currentTarget = e.currentTarget;
// Give browser time to focus the next element
requestAnimationFrame(() => {
// Check if the new focused element is a child of the original container
if (!currentTarget.contains(document.activeElement)) {
onBlur();
}
});
},
[onBlur]
);
return (
<div {...props} onBlur={handleBlur} tabIndex={-1}>
{children}
</div>
);
};
// ----- Demo boilerplate code
const App = () => {
const [isFocusInElement, setIsFocusInElement] = useState(false);
return (
<div className="container">
<div className="column">
<button className="button button--outside">Outside</button>
</div>
<ChildrenBlur
// This will trigger when blur leaves the element and it's children
onBlur={() => setIsFocusInElement(false)}
// onFocus works as expected natively
// but it will be triggered for each focused child element
onFocus={() => setIsFocusInElement(true)}
className={`group ${isFocusInElement ? "group--focused" : ''}`}
>
<button className="button button--inside">Button 1</button>
<button className="button button--inside">Button 2</button>
<button className="button button--inside">Button 3</button>
Focus is {isFocusInElement ? 'in' : 'out of'} the group.
</ChildrenBlur>
<div className="column">
<button className="button button--outside">Outside</button>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
View Compiled
This Pen doesn't use any external CSS resources.