Please note that I'm using iOS 17.4, cannot update and have no other device with a somewhat recent Safari available.
Preamble
In a small hobby project I'm working on I have a section containing some text and the title of what you're currently viewing. The title can be edited using a separate, hidden-until-needed, text input field. For the sake of simplicity, and because it was the easiest to implement, the normal title is shown until you hover over it, or you tab into the input field, at which point it will hide the title and show the input field, essentially replacing it.
It might seem easy to swap two elements out on hover using something like display: none
. However, that doesn't work at all when I want to focus the element using the tab key when it is not displayed at all. So, here we are...
Theoretical implementation
Only if the parent .switchOnHover
isn't hovered AND the children aren't focused or similar, then keep the default, otherwise swap it.
This means that what is true for .onhover
needs to be the complete opposite for .nonehover
, hence the :not
selectors.
There's also the constraint that I won't use a second CSS rule to "undo" the hiding process, as I don't know the previous styles applied. (it's a universal rule that can be used for more than just this one part).
The code
The smallest demonstration of the issue at hand. The supposed-to-be visible element is marked in red.
.switchOnHover:not(:hover):not(:has(:active, :focus, :focus-within)) > .nonehover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
background-color: red;
}
<div class="switchOnHover">
<div class="nonehover">Hello</div>
<div class="onhover" tabindex="0">World</div>
</div>
Please note that I'm using iOS 17.4, cannot update and have no other device with a somewhat recent Safari available.
Preamble
In a small hobby project I'm working on I have a section containing some text and the title of what you're currently viewing. The title can be edited using a separate, hidden-until-needed, text input field. For the sake of simplicity, and because it was the easiest to implement, the normal title is shown until you hover over it, or you tab into the input field, at which point it will hide the title and show the input field, essentially replacing it.
It might seem easy to swap two elements out on hover using something like display: none
. However, that doesn't work at all when I want to focus the element using the tab key when it is not displayed at all. So, here we are...
Theoretical implementation
Only if the parent .switchOnHover
isn't hovered AND the children aren't focused or similar, then keep the default, otherwise swap it.
This means that what is true for .onhover
needs to be the complete opposite for .nonehover
, hence the :not
selectors.
There's also the constraint that I won't use a second CSS rule to "undo" the hiding process, as I don't know the previous styles applied. (it's a universal rule that can be used for more than just this one part).
The code
The smallest demonstration of the issue at hand. The supposed-to-be visible element is marked in red.
.switchOnHover:not(:hover):not(:has(:active, :focus, :focus-within)) > .nonehover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
background-color: red;
}
<div class="switchOnHover">
<div class="nonehover">Hello</div>
<div class="onhover" tabindex="0">World</div>
</div>
There's also a second variation with the same issue. Here the code actually "hides" the element (which is why .onhover
and .nonehover
are swapped).
.switchOnHover:not(:is(:hover, :has(:active, :focus, :focus-within))) > .onhover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .nonehover {
height: 0;
width: 0;
bottom: 100%;
right: 100%;
opacity: 0;
position: absolute;
user-select: none;
pointer-events: none;
overflow: hidden;
}
<div class="switchOnHover">
<div class="nonehover">Hello</div>
<div class="onhover" tabindex="0">World</div>
</div>
On Safari, the first code snippet doesn't unmark the first element and has both highlighted; and the second one doesn't swap on hover at all (except using external mouse, and then it flickers), but does swap when using the tab key to focus the other element.
And the most frustrating part is that when the two :not()
s are seperated, it does work! Well, you need to both make it active and hovered, but even that doesn't work with the code snippets above this one.
.switchOnHover:not(:hover) > .nonehover {
outline: solid 6px cyan;
}
.switchOnHover:not(:has(:active, :focus, :focus-within)) > .nonehover {
background-color: lime;
}
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
background-color: red;
}
<div class="switchOnHover">
<div class="nonehover">Hello</div>
<div class="onhover" tabindex="0">World</div>
</div>
I want to know how to keep it in the shortest form possible while working as expected and (optionally) why WebKit handles this differently. Could anyone help out?
Edit 1: Added second shortest demonstration.
Edit 2: Expanded Preamble, on request, to further explain the situation.
Share Improve this question edited Mar 25 at 0:11 Lopolin asked Mar 23 at 1:33 LopolinLopolin 155 bronze badges 7 | Show 2 more comments1 Answer
Reset to default 0The easiest solution to implement is not to swap elements at all, just swap styles. Use a regular text input which mimics the appearance of a heading, but when it’s hovered or has the focus then it’s styled to look like an input box.
body {
margin: 0 2em;
}
h1 input {
--border-width: 2px;
--padding-left: 0.3em;
/* make the input inherit font style from its parent */
font-family: inherit;
font-weight: inherit;
font-size: inherit;
/* set it up to look like a text box, but use a transparent border color so it appears just like a regular heading */
width: 100%;
padding-left: var(--padding-left);
border: var(--border-width) solid transparent;
border-radius: 0.25em;
transition: 0.2s;
outline: none;
/* shift it to the left so it lines up with a regular heading */
position: relative;
left: calc((var(--border-width) + var(--padding-left)) * -1);
}
/* on hover */
h1 input:hover, h1 input:active, h1 input:focus {
border-color: #888;
background: #fafafa;
box-shadow: inset 0 0 5px rgb(0 0 0 / 0.2);
}
<h1>A regular title</h1>
<h1><input type="text" value="An editable title"></h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam. Cras sit amet pharetra ante. Sed quis commodo quam, vel facilisis ipsum. Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed. Donec interdum sit amet est non accumsan. Donec non augue feugiat, fermentum nunc non, convallis est. Cras vel ligula nec odio faucibus ultricies. Sed vulputate tortor eget pretium convallis. Cras interdum elit eget mi porta suscipit. Morbi ut velit diam. Etiam finibus eros et efficitur rutrum. Quisque viverra metus ac eleifend imperdiet. Quisque pretium ut purus vitae tempus. Duis varius risus congue velit faucibus, sed interdum purus consectetur.</p>
Or a slightly simpler solution is to use outline
instead of border
.
body {
margin: 0 2em;
}
h1 input {
--padding-left: 0.3em;
/* make the input inherit font style from its parent */
font-family: inherit;
font-weight: inherit;
font-size: inherit;
/* set it up to look like a text box, but use a transparent border color so it appears just like a regular heading */
width: 100%;
padding-left: var(--padding-left);
border: 0;
border-radius: 0.25em;
transition: 0.2s;
/* shift it to the left so it lines up with a regular heading */
position: relative;
left: calc(var(--padding-left) * -1);
}
/* on hover */
h1 input:hover, h1 input:active, h1 input:focus {
outline: 2px solid #888;
background: #fafafa;
box-shadow: inset 0 0 5px rgb(0 0 0 / 0.2);
}
<h1>A regular title</h1>
<h1><input type="text" value="An editable title"></h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam. Cras sit amet pharetra ante. Sed quis commodo quam, vel facilisis ipsum. Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed. Donec interdum sit amet est non accumsan. Donec non augue feugiat, fermentum nunc non, convallis est. Cras vel ligula nec odio faucibus ultricies. Sed vulputate tortor eget pretium convallis. Cras interdum elit eget mi porta suscipit. Morbi ut velit diam. Etiam finibus eros et efficitur rutrum. Quisque viverra metus ac eleifend imperdiet. Quisque pretium ut purus vitae tempus. Duis varius risus congue velit faucibus, sed interdum purus consectetur.</p>
:not
selectors. There’s very likely a way to implement the effect you want without using:not
at all, but in order to be sure I need to be clearer on what exactly you are trying to implement. Please edit your post to clarify this. – Brett Donald Commented Mar 23 at 7:22:not
selectors aren't in the same line. You can see in the first snippet that it uses two:not
selectors in the same line. – Lopolin Commented Mar 25 at 0:17