Consider the following HTML structure:
<input type=radio name=picker value=foo> <input type=radio name=picker value=bar> etc
<ul>
<li class=foo>Foo #1</li>
<li class=bar>Bar #1</li>
<li class=foo>Foo #2</li>
<li class=bar>Bar #2</li>
</ul>
I want to style those list items which match the currently-selected radio button. This works if all the possible radio button values are hard-coded:
input:checked[value=foo] ~ ul li[class~=foo] {
background: blue;
color: white;
}
input:checked[value=bar] ~ ul li[class~=bar] {
background: blue;
color: white;
}
Is there any way to say "input:checked" and then "li[class~=[the value of the currently-selected input]]" ? Assume that the radio buttons and list items are all dynamically generated.
If all else fails, I can dynamically generate the CSS too, but that seems unideal.
This seems to be a fundamental limitation of CSS (as of 2019 - if this changes in the future, someone can post a different answer). Since the input controls have to be script-generated anyway, it's simplest to script-generate the CSS as well.
Related
I have created a JavaScript-free, HTML- and CSS-only drop-down menu using the checkbox hack. I am trying to avoid using JS as some users disable it. For touch-screens and mouse it works fine, but I am attempting to make it work so that anyone using keyboard navigation can use it.
It does work more or less:
The [Enter] key opens links and [spacebar] opens some (see further) sub-menus using checkboxes which are invisible to the naked eye by using the class opacity: 0;.
However, I have two contradictory problems with keyboard navigation:
The <a> tags and pseudo ::after elements associated with some checkboxes focus visibly with a faint dotted highlight but do not open when the spacebar is pressed.
When using a <span> tag instead of an <a> tag, the menus open when the spacebar is pressed but they are not visibly 'focused' with the dotted outline.
Also, the 'invisible' checkbox is positioned behind the ::after pseudo element of '+' but, because it's invisible, there is no focus outline.
Here is my simplified code with examples of the different combinations I have tried:
ul.menubar {
padding: 1rem 0 0 0.5rem;
}
[id^=chk] {
opacity: 0;
}
[id^=chk]+ul {
display: none;
}
[id^=chk]:checked+ul {
display: block;
}
label.link::after,
label span.separator::after,
label a.separator::after {
position: relative;
content: "+";
right: -1.25rem;
top: 0;
}
a {
text-decoration: none;
}
li {
line-height: 2.5rem;
font-size: 1.25rem;
list-style: none;
}
li a,
li span,
li label,
li input,
.inline {
display: inline;
position: relative;
}
<body>
<nav>
<ul class="menubar" role="menubar">
<li>
<div class="inline">Item link plus dropdown using span. + works but no keyboard focus |
<label for="chk-101" class="link" title="Item 1 link" aria-label="Item 1 link"></label></div><input id="chk-101" type="checkbox" class="chkbox" tabindex="1" aria-checked="false">
<ul>
<li>Dropdown 1a</li>
<li>Dropdown 1b</li>
</ul>
</li>
<li>Item link plus dropdown using 'a' tag with href; + works but not keyboard focused |
<label class="link" for="chk-102"></label><input id="chk-102" type="checkbox" class="chkbox" tabindex="1" aria-checked="false">
<ul>
<li role="menuitem" tabindex="1">Dropdown 2a</li>
<li role="menuitem" tabindex="1">Dropdown 2b</li>
</ul>
</li>
<li>
<label for="chk-103"><span class="separator">Separator with span - does not focus</span></label><input id="chk-103" type="checkbox" class="chkbox" tabindex="1" aria-checked="false">
<ul>
<li role="menuitem" tabindex="1">Dropdown 3a</li>
<li role="menuitem" tabindex="1">Dropdown 3b</li>
</ul>
</li>
<li>
<label for="chk-104">Separator with href - focus does not respond to spacebar</label><input id="chk-104" type="checkbox" class="chkbox" tabindex="-1" aria-checked="false">
<ul>
<li role="menuitem" tabindex="1">Dropdown 3a</li>
<li role="menuitem" tabindex="1">Dropdown 3b</li>
</ul>
</li>
<li>
<label for="chk-105"><span class="separator" role="checkbox" tabindex="1" aria-checked="false" aria-label="Separator 2">Separator with span role="checkbox"; focus but does not respond to spacebar</span></label>
<input id="chk-105" type="checkbox" class="chkbox" tabindex="-1" aria-checked="false">
<ul>
<li role="menuitem" tabindex="1">Dropdown 3a</li>
<li role="menuitem" tabindex="1">Dropdown 3b</li>
</ul>
</li>
</ul>
</nav>
</body>
Short Answer
The checkbox hack is not a good practice, especially for navigational menus and is not semantically correct, generally causing accessibility nightmares.
As "JavaScriptless" users are around 1.3% of users you should provide a usable but perhaps ugly version of the menu for them and make sure that for the majority of users everything is semantically correct.
Please note: The advice here is for navigational drop-downs, if this is for a complex application where the drop-down triggers functions on the current page rather than navigation then other patterns may be better.
Long Answer
I would normally address the question being asked but you are making things very difficult for yourself and I think it would be better to provide an alternative.
I have listed the reasons why not to use the checkbox hack and provided an explanation of a much simpler, more robust and much more accessible solution which should hopefully help you going forward.
First, the bad news
I hate to say it but you will never make the above accessible without JavaScript and your current implementation has quite a few accessibility issues.
First of all aria-checked needs to be toggled and you can only do that with JavaScript. Also a checkbox is not a logical / semantically correct element to use here and does not convey the right information to screen readers. If you then add role="button" to counter this then aria-checked is not a valid attribute.
Secondly the "checkbox hack" is not intended for navigational menus it is intended to be used for complex menus as part of an application, it is still not a good pattern to use then and should only be used if you are really struggling to make other options work.
Thirdly pseudo elements (your "+" symbol) are not focusable and a lot of screen readers ignore them / don't behave well with them, so that is a big accessibility problem.
Fourthly anywhere where you are using <a href="#" is an accessibility anti-pattern. Anchors should only be used for navigation, <button> elements are for same page actions / functions. This is down to how they are announced to screen readers and expected behaviour. If you use a hyperlink it must contain a full and valid href either to an anchor on the current page or an entirely new page.
There are other issues but hopefully you get the idea!
Never fear, there is an easier way to do this!
Your main concern is that your menu works without JavaScript, which is causing you to choose hacks over the best and easiest ways to do things.
Here is the simplest way to create an accessible experience for all.
Create an HTML sitemap with anchors at key points.
Make the "button" an anchor and point the href to the relevant part of the sitemap.
Use JavaScript to intercept the request and open the drop-down menu / toggle aria attributes for the majority of users who have JavaScript enabled.
This way there is still a way to navigate the site if JavaScript is disabled, but for majority of users you have a drop-down menu.
Rough example
In the below example I have covered most accessibility issues.
No JavaScript
The raw HTML is valid and points to an HTML sitemap (which in the example is simulated with anchors further down the page, you should obviously have this on another page!).
If your sitemap is particularly large then you should use ids at the relevant parts of the page and link to those anchors directly (i.e. yoursite.com/sitemap#a-particular-category-or-main-menu-item). I have included this as part of the example as well.
To test the above I have included a checkbox that removes all the relevant event handlers and aria attributes so you can experience the "javaScriptless" experience.
With JavaScript
If JavaScript is available then we add the relevant role="button" to the link, we also add the aria-expanded attribute that we can later toggle to tell screen reader users if the menu is open or not and the aria-haspopup attribute so they know that the "button" will open a popup.
As we have told screen readers that the hyperlink is now a button with role="button" we allow them to activate the "button" with the space key as that is expected behaviour.
Finally they can close the drop-down with the Esc key as a "nice to have", this is not essential for navigational menus as they should not trap focus but I always like to add it, although I haven't dealt with returning focus to the parent item (something for you to consider).
I also ensured that the tap target was 44px by 44px to ensure it passes 2.5.5 tap target size as that was another issue with your example.
As a "+" is not very informative for screen reader users I also added some visually hidden text to explain what the toggle button does. I also toggle this button text depending on whether the drop-down is open or closed. This is done at the same time as toggling aria-expanded to make things perfectly clear for screen reader users.
There may be things I have missed in the following example so please test it thoroughly before using it in production. Also apologies I wrote this as I thought of things that needed addressing so the code is probably a bit messy.
var toggles = document.querySelectorAll(".menu-toggle");
// add the relevant aria, role and handler
var init = function(){
for(var x = 0; x < toggles.length; x++){
var el = toggles[x];
el.setAttribute("role", "button");
el.setAttribute("aria-expanded", "false");
el.setAttribute("aria-haspopup", "true");
el.addEventListener('click', openToggle);
el.addEventListener('keydown', keydownHandler);
document.addEventListener('keydown', closeAllHandler);
}
}
init();
function keydownHandler (e) {
//space key
if (e.keyCode === 32) {
openToggle(e);
}
}
function closeAllHandler(e){
// esc key
if (e.keyCode === 27) {
var openItems = document.querySelectorAll(".open");
for(var x = 0; x < openItems.length; x++){
openItems[x].classList = "";
}
}
}
//handler for when the "+" button is pressed, the second parameter is a quick way to recycle the function as a close function
function openToggle(e, close){
e.preventDefault();
var self = e.currentTarget;
var dropDown = getNextSibling(self, "ul");
if(dropDown.classList == "open" || close){
dropDown.classList = "";
self.setAttribute("aria-expanded", "false");
self.querySelector('.icon').innerHTML = "+";
self.querySelector('.toggleText').innerHTML = "show submenu";
}else{
dropDown.classList = "open";
self.setAttribute("aria-expanded", "true");
self.querySelector('.icon').innerHTML = "-";
self.querySelector('.toggleText').innerHTML = "close submenu";
}
}
// helper function to grab next sibling by selector.
var getNextSibling = function (el, sel) {
var sib = el.nextElementSibling;
if (!sel) return sib;
while (sib) {
if (sib.matches(sel)) return sib;
sib = sib.nextElementSibling
}
};
//////////////////////////////DEMO ONLY NOT NEEDED IN PRODUCTION///////////////////////////////////
//just for the demo, allows usa to simulate JavaScript being switched off
document.getElementById("jsActivated").addEventListener('change', adjustJS);
function adjustJS(e){
if (!e.target.checked) {
init();
} else {
destroy();
}
};
// just for the demo, used to remove event listener and role
var destroy = function(){
for(var x = 0; x < toggles.length; x++){
var el = toggles[x];
el.removeAttribute("role");
el.removeAttribute("aria-expanded");
el.removeAttribute("aria-haspopup");
el.removeEventListener('click', openToggle);
el.removeEventListener('keydown', keydownHandler);
}
}
.has-submenu>ul{
display: none;
}
.has-submenu>ul.open{
display: block;
}
li{
padding: 10px;
}
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
.menu-toggle{
height: 44px;
width: 44px;
outline: 2px solid #666;
display: inline-block;
line-height: 44px;
font-size: 25px;
text-align: center;
text-decoration: none;
margin-left: 10px;
}
<label>Simulate JavaScript Off?
<input type="checkbox" id="jsActivated"/>
</label>
<h1>Example Accessible Menu</h1>
<nav aria-label="Main Navigation">
<ul>
<li class="has-submenu">
Some Category
<a class="menu-toggle" href="#sitemap-item1">
<span class="icon">+</span>
<span class="visually-hidden">
<span class="toggleText">show submenu</span> for "Some Category"
</span>
</a>
<ul>
<li>Some Category Item 1</li>
<li>Some Category Item 2</li>
</ul>
</li>
<li class="has-submenu">
This is a different category
<a class="menu-toggle" href="#sitemap-item2" aria-expanded="false" aria-haspopup="true">
<span class="icon">+</span>
<span class="visually-hidden">
<span class="toggleText">show submenu</span> for "This is a different category"
</span>
</a>
<ul>
<li>Different Category Item 1</li>
<li>Different Category Item 2</li>
</ul>
</li>
</ul>
</nav>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<strong>Scroll up to get back to menu, this is to simulate another page</strong>
<h2>The sitemap located on another page</h2>
<ul>
<li id="sitemap-item1">Some Category
<ul>
<li>Some Category Item 1</li>
<li>Some Category Item 2</li>
</ul>
</li>
<li id="sitemap-item2">This is a different category
<ul>
<li>Different Category Item 1</li>
<li>Different Category Item 2</li>
</ul>
</li>
</ul>
<strong>Scroll up to get back to menu, this is to simulate another page</strong>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
Codepen: https://codepen.io/anon/pen/LKwQoe
I have the following code:
.required:after {
position: absolute;
color: red;
content: "*";
margin-left: 4px;
}
.myDiv {
background-color: tan;
width: 200px;
}
<div class="myDiv">
<ul>
<li class="required">One</li>
<li class="required">Two</li>
<li class="required">Three</li>
<li class="required">Four</li>
<li class="required">
Five turns out to be long
</li>
</ul>
</div>
With that last li tag, my "required" asterisk gets punted down one line. However, if I write that same exact tag one line like this:
<li class="required">Five turns out to be long</li>
...the asterisk stays on the same line. It seems to be that the first version of the code generates this HTML:
<li class="required">
"
Five turns out to be long
"
::after
</li>
...and the other example generates this:
<li class="required">
"Five turns out to be long"
::after
</li>
I don't understand exactly what's going on here and I lack the language to explain to my coworkers why I need to change some of their tags to be on only one line. What exactly is this behavior, and is it documented anywhere? Thanks!
I need to make a website for school. I try to use the Checkbox Hack. It works in general, but i cant implement it in my website. Here is my problem: input[type=checkbox]:checked > .menuitem doesn't seem to effect the folowing:
<input type="checkbox" id="toggle-1">
<nav>
<ul>
<li><label for="toggle-1"><img src="images/menu.png" height="38" width="38"/></label></li>
<li class="menuitem">About us</li>
</ul>
</nav>
I tried replacing my "> .menuitem" in input[type=checkbox]:checked > .menuitem with various things like ~ and ~ nav>ul>li But i dont have any succes. Does anybody have an idea what i need to do?
i'm looking for a effect that does this:
If (checkbox is checked) {
Hide a part of the webpage
}
On another note: I am not allowed to use anything else then HTML and CSS (So no Javascript or php etc.)
try this
input[type=checkbox]:checked + nav .menuitem
input[type=checkbox]:checked + nav .menuitem{
display:none;
}
<input type="checkbox" id="toggle-1">
<nav>
<ul>
<li><label for="toggle-1"><img src="images/menu.png" height="38" width="38"/></label></li>
<li class="menuitem">About us</li>
</ul>
</nav>
The > css selector is immediate child selector, so browser search only for the first children of input tag - in this case nav element.
All you need is:
input[type=checkbox]:checked .menuitem
Eventually you can add additional tags to be more specific
input[type=checkbox]:checked nav .menuitem
Or
input[type=checkbox]:checked nav ul li.menuitem
I have the following problem:
I have a ul with blue list item text and a blue custom bullet. Now if the user clicks a li the text should be black and the custom bullet as well.
The text is easy to change, but i don't know how i could do it, that the custom bullet also stays black.
When the user hovers trough the menu the custom bullets and the text turns black. This is already working. But if the user is on a page, as soon he leaves the hover area the bullets turn blue again. This shoud not be the case.
Here is what i have already:
<ul class="listMenuItem--s">
<li class="listMenuChild">
Dienstleistungen
<ul class="sub-nav">
<li>Treuhand & Finanzen </li>
<li>Wirtschaftsprüfung</li>
<li>Unternehmensberatung / Nachfolge</li>
<li>Pensionierungs-, Vorsorge- und Steuerplanung</li>
<li>Informatik</li>
</ul>
</li>
<li class="listMenuChild">
Über uns
</li>
<li class="listMenuChild">
Publikationen
</li>
<li class="listMenuChild">
Hilfsmittel
</li>
<li class="listMenuChild">
Kontakt / Lageplan </li>
</ul>
And here the CSS.
.listMenuChild {
list-style-image: url("../img/bg_li.png");
margin-top: 5%;
}
.listMenuChild:active {
list-style-image: url("../img/bg_li_h.png");
}
.listMenuChild:hover {
list-style-image: url("../img/bg_li_h.png");
}
Is it because :active and :hover don't work together?
Any help much appreciated
:active means "While being clicked on or otherwise activated" (e.g. a focusable element would also be activated while you press the Enter key when it has a focus). Note while, not after.
It doesn't seem to have any effect for you because you never activated it without also hovering it (and your hover rule comes after the active rule).
It sounds like when you say "When the list item is active" you mean "When the link inside the list item has an href attribute that resolves to the URL of the current page". That isn't something you can express with CSS.
Use server side code to add a class to the list item based on the page you are loading and target that class with CSS.
If you have an :active class on a non-focussable item like <li>, it works only during mousedown. If you need to make it work without :hover, make it focussable using tabindex.
<li class="listMenuChild" tabindex="0">
The above code is only for it to stay. i.e., Currently the browser applies :active for only mousedown event. So that occurs only when you are having both :hover:active pseudo class:
.listMenuChild:hover:active {backgkround: #999;}
As you are using pages for each item you could create another class, add it to the list item for each page where that list item is to be highlighted for that page:
.listMenuChild {
list-style-image: url("../img/bg_li.png");
margin-top: 5%;
}
.listMenuChildActive {
list-style-image: url("../img/bg_li_h.png");
}
.listMenuChild:hover {
list-style-image: url("../img/bg_li_h.png");
}
HTML example:
<ul class="listMenuItem--s">
<li class="listMenuChildActive">
Dienstleistungen
<ul class="sub-nav">
<li>Treuhand & Finanzen </li>
<li>Wirtschaftsprüfung</li>
<li>Unternehmensberatung / Nachfolge</li>
<li>Pensionierungs-, Vorsorge- und Steuerplanung</li>
<li>Informatik</li>
</ul>
</li>
<li class="listMenuChild">
Über uns
</li>
<li class="listMenuChild">
Publikationen
</li>
<li class="listMenuChild">
Hilfsmittel
</li>
<li class="listMenuChild">
Kontakt / Lageplan </li>
</ul>
Try swapping these two lines:
.listMenuChild:hover {
list-style-image: url("../img/bg_li_h.png");
}
.listMenuChild:active {
list-style-image: url("../img/bg_li_h.png");
}
Remember this mnemonic phrase:
LoVe and H Ate
The 4 pseudo-selectors:
a:link // **L**ove
a:visited // Lo**V**e
a:hover // **H**ate
a:active // H**A**te
If they are not in that specific order, they probably will not work correctly.
https://css-tricks.com/remember-selectors-with-love-and-hate/
I have multiple nav ul li a that i need to style different and I have no good way of doing this. I've looked around the net for quite a bit but i do not understand how to do this without using class="" in every element. My code is below. There must be a better way of doing this? Like all children that has class="loginmenu" should be like x and all children of class="dropdownmenu" should be like y. Even if they are the same element.
<nav class="loginmenu">
<ul class="loginmenu">
<li class="loginmenu">
<p>Login</p>
</li>
</ul>
</nav>
<nav class="dropdownmenu">
<ul>
<li class="gigs">
<p>Gigs</p>
<p class="subtext">Shows & Gigs</p>
</li>
<li class="music">
<p>Music</p>
<p class="subtext">Tracks & Sets</p>
</li>
<li class="booking">
<p>Booking</p>
<p class="subtext">Booking & Contact</p>
</li>
CSS:
nav.loginmenu {
position: absolute;
}
li.loginmenu{
font-size: 25px;
margin-left: 1200px;
} and so on...
You can use nav.loginmenu <element>.
nav.loginmenu li {
font-size: 25px;
margin-left: 1200px;
}
For more information see the documentation of CSS selectors or try CSS Selector tester.
CSS rules can represent a hierarchy. For instance, the following means: "Apply this rule to all li elements that are inside a nav element with class loginmenu")
nav.loginmenu li {
..
}
It's commonplace for me to add classes to one root element that has no rules of its own, but for which having that class affects behavior of its children.
When sharing functionality, it is also common to add one class to multiple elements, or multiple classes to one element (separated by spaces) if it simply represents certain behavior (eg, applying a margin to all list elements to give them a "tabbing" look)
Additionally, many properties (most of the font-... properties for instance) are inherited from parent to child unless they're overridden at a lower level, so there's no need to repeat those for further descendants.
Not exactly sure what you are trying to do here. But if I'm interpreting correctly, you want to target different elements within each <nav> element? If so you can add an id which should be unique (not repeated) to your <nav> element then target the element like so:
html:
<nav id="loginMenu">
...
</nav>
css:
#loginMenu li {
font-size: 25px;
margin-left: 1200px;
}
Or you can use Genhis answer.