Published date and last update:
CSS for Visually Hidden HTML
To visually hide HTML elements with CSS, this is still the most comprehensive post I’ve found on this, adressing several cases not discussed anywhere else: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/
Some details of the rules are further explored here, where it’s also noted that :active must be considered in Safari: https://www.tpgi.com/the-anatomy-of-visually-hidden/
The latter made me realize that separating into two variants, like .visually-hidden and .visually-hidden-focusable, is not wise since a visually hidden element should show if it can have focus and has focus—for not screen reader users.
Another functionality I’m testing is to be able set a container to visually-hidden, but not when any children has focus using :has(). Simply using :focus-within does not work (perhaps because it does not work within :not(), I’m not sure), like .visually-hidden:not(:focus):not(:active):not(:focus-within).
Then, also incorporating a technique using aria attributes instead of classes used in sanitize.css, it results in the following snippet:
.visually-hidden:not(:focus):not(:active),
[aria-hidden="false" i][hidden]:not(:focus):not(:active) {
position: absolute !important;
white-space: nowrap !important;
padding: 0 !important;
margin: -1px !important;
border: 0 !important;
overflow: hidden !important;
width: 1px !important;
height: 1px !important;
clip: rect(1px, 1px, 1px, 1px) !important;
clip-path: inset(50%) !important;
-webkit-clip-path: inset(50%) !important;
}
@supports selector(:has(+ *)) {
.visually-hidden:not(:focus):not(:active):not(:has(:focus, :active)),
[aria-hidden="false" i][hidden]:not(:focus):not(:active) {
position: absolute !important;
white-space: nowrap !important;
padding: 0 !important;
margin: -1px !important;
border: 0 !important;
overflow: hidden !important;
width: 1px !important;
height: 1px !important;
clip: rect(1px, 1px, 1px, 1px) !important;
clip-path: inset(50%) !important;
-webkit-clip-path: inset(50%) !important;
}
}
[aria-hidden="false" i][hidden] {
display: initial;
}
How does it work
Let’s start at the top. Some of the rules are quite obvious.
position: absoluteto remove the element from the document flow so it does not affect layout.white-space: nowrapto stop the element content from wrapping to another line.padding: 0for no padding.margin: -1pxfor no margin?!border: 0for no border, but why not useborder: none?overflow: hiddento hide any content ‘overflowing’ the element’s bounds, together withwidthandheight: 1px, but why not use0?clipandclip-path?