<div class="row">
<h1>Use TAB or hover an element</h1>
</div>
<div class="row">
<div>General div: Not focusable</div>
<div tabindex="0">Div with tabIndex > -1: focusable</div>
<ul>
<li>general li: not focusable</li>
<li tabindex="0">li with tabIndex: focusable</li>
</ul>
<details>
<summary>Details: Focusable</summary>
some text
<a href="#">anchor inside details: focucalbe</a>
</details>
</div>
<div class="row">
<input type="text" value="text input: focusable" />
<select>
<option selected>General Select: focusable</option>
<option>Text 2</option>
<option disabled>Disabled Option</option>
<option>Text 3</option>
</select>
<input type="submit" disabled value="Disabled input[type=submit]: not focusable" />
<div>
<label>
<input type="checkbox" checked /> checkbox
</label>
</div>
<div>
<button type="button">
general button: focusable
</button>
</div>
<div>
<button disabled>
disabled button: not focusable
</button>
</div>
</div>
<div class="row">
<label for="idArea">
Label for Textarea
</label>
<textarea id="idArea">Testarea: focusable</textarea>
<label>
Textarea inside label
<textarea>Testarea: focusable</textarea>
</label>
<a href="#">
Geteral anchor: focusable
</a>
<a>
Anchor without href: not focusable
</a>
</div>
<div class="overlay" id="overlay">
<div class="border border--leftTop" id="leftTop"></div>
<div class="border border--leftBottom" id="leftBottom"></div>
<div class="border border--rightTop" id="rightTop"></div>
<div class="border border--rightBottom" id="rightBottom"></div>
</div>
body {
background: #1B2B34;
display: flex;
flex-direction: column;
justify-content: center;
color: #f5f5f5;
width: 100vw;
height: 100vh;
overflow: hidden;
}
button:disabled,
input:disabled {
color: #AFC8D5;
}
a {
color: #B9AED5;
}
*:not(#fake-id) {
outline: 0;
}
.row {
display: flex;
padding: 20px;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
& > * {
margin: 5px;
}
}
.overlay {
pointer-events: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
transition: opacity 0.1s;
}
.border {
position: absolute;
top: -4px;
left: -4px;
transform: translate(-20px, -20px);
transition: transform;
transition-duration: 0.1s;
border-style: solid;
border-width: 1px;
border-color: red;
width: 8px;
height: 8px;
&--leftTop {
transform: translate3d(-20px, -20px, 1px);
border-bottom-color: transparent;
border-right-color: transparent;
}
&--leftBottom {
transform: translate3d(-20px, calc(100vh + 20px), 1px);
border-top-color: transparent;
border-right-color: transparent;
}
&--rightTop {
transform: translate3d(calc(100vw + 20px), -20px, 1px);
border-bottom-color: transparent;
border-left-color: transparent;
}
&--rightBottom {
transform: translate3d(calc(100vw + 20px), calc(100vh + 20px), 1px);
border-top-color: transparent;
border-left-color: transparent;
}
}
View Compiled
let rect;
let current;
const overlay = document.querySelector('#overlay');
const leftTop = document.querySelector('#leftTop');
const leftBottom = document.querySelector('#leftBottom');
const rightTop = document.querySelector('#rightTop');
const rightBottom = document.querySelector('#rightBottom');
const translate = (x, y) => `transform: translate(${x}px, ${y}px)`;
const show = (element) => {
const rect = element?.getBoundingClientRect();
let opacity = 0;
if (rect) {
opacity = 1;
leftTop.setAttribute('style', translate(rect.left, rect.top));
leftBottom.setAttribute('style', translate(rect.left, rect.bottom));
rightTop.setAttribute('style', translate(rect.right, rect.top));
rightBottom.setAttribute('style', translate(rect.right, rect.bottom));
}
overlay.setAttribute('style', `opacity: ${opacity}`);
};
const hide = () => {
show();
};
const checkHref = (element) => {
const tag = element.tagName;
return !(tag === 'A' || tag === 'AREA') || element.href;
};
const onFocus = (event) => {
if (
event?.target?.tabIndex > -1
&& current !== event.target
) {
// skip anchors and areas without href and disabled items
if (!checkHref(event.target) || event.target.disabled) {
return;
}
current = event.target;
show(current);
}
};
const onBlur = (event) => {
if (event?.target?.tabIndex > -1) {
// find parent item that can be focusable
current = current?.parentNode;
while (current && current.tabIndex < 0) {
current = current.parentNode;
}
if (current === document) {
current = null;
}
// return focus to activeElement
if (!current && document.activeElement && document.activeElement !== document.body) {
current = document.activeElement;
}
if (current) {
show(current);
} else {
hide();
}
}
};
document.addEventListener('focus', onFocus, true);
document.addEventListener('blur', onBlur, true);
document.addEventListener('pointerenter', onFocus, true);
document.addEventListener('pointerleave', onBlur, true);
// don't forget to remove subscription
// document.removeEventListener('focus', onFocus, true);
// document.removeEventListener('blur', onBlur, true);
// document.removeEventListener('pointerenter', onFocus, true);
// document.removeEventListener('pointerleave', onBlur, true);
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.