A decided improvement, but still has the issue that your anchors are in the markup, making no sense for screen readers or users with scripting blocked. If you insist on using JavaScript for this, you should likely be generating those from the scripting and not the markup.

Likewise you're using display:none which means search engines, non-visual UA's , and so forth will ignore the content. The "closed" state should probably also be added by the script if you're going to use display:none. You can thank the black-hat SEO dirtbags and the practice of "content cloaking" from the early '00's for that one.

It would also help if you stopped using PX metrics on fonts, paddings, sizings, etc. It's a static measurement that tells users with accessibility needs to go suck an egg.

And if you're gonna vanilla, SCSS isn't vanilla either. :p

I was thinking on this, and it hit me that if you’re going to use JavaScript — or even if you don’t — it’s too hard-coded to work on lists. I was thinking that if a class were used on the last element to be shown all the time, you could DOM walk to its parent, add the anchor for the collapse off that element’s parent, and plug in all the other appropriate classes in a markup neutral manner. Then for the show/hide CSS could handle the heavy lifting.

<li>Tomato 🍅</li>
<li class="collapseAfter">Grapes 🍇</li>
<li>Watermelon 🍉</li>
<li>Mango 🥭</li>
<li>Peach 🍑</li>
<li>Broccoli 🥦</li>
<li class="collapseAfter">Corn 🌽</li>
<li>Carrot 🥕</li>
<li>Eggplant 🍆</li>

Doesn’t even need the wrapping DIV anymore.

(function() {  var collapseHooks = document.getElementsByClassName('collapseAfter');  for (var hook of collapseHooks) {    var
wrap = hook.parentNode,
a = wrap.parentNode.insertBefore(
a.addEventListener('click', toggleCollapse, false);
a.className = 'collapseAnchor';
a.href="#"; /* without this it's not keyboard focusable */
wrap.classList.add('closed', 'collapseWrap');
} // for hook function toggleCollapse(e) {
} // toggleCollapse

A IIFE so it is scope isolated, grabs the collapseAfter hooks, grabs the wrapping parentNode, puts an anchor after that parentNode for our show/hide, then puts a ‘closed’ class on the wrapper to close the children, and a ‘collapseWrap’ class as a trigger so that our CSS is only applied when JavaScript is working.

The toggleCollapse method is pretty simple since again on something like this, just do a class swap and let the CSS do the real work.

body {
font-family: sans-serif;
li {
.collapseAnchor {
.collapseWrap + .collapseAnchor:before {
content:"See Less";
.collapseWrap.closed + .collapseAnchor:before {
content:"See More";
.collapseAnchor:after {
padding:0.4em 0 0 0.3em;
.collapseWrap.closed + .collapseAnchor:after {
.collapseWrap.closed > .collapseAfter ~ * {

The real magic being the use of the adjacent sibling selector to close all elements after .collapseAfter when the parent has.close

I put the :before text separate from the :after arrow so that it could be styled and sized. Inline-block lets us use flow and top alignment rather than trying to play with absolute positioning when trying to compensate for UTF-8 triangles having shite alignment.

Note I use position:absolute; to hide things instead of display:none; so that search engines, screen readers, braille readers, and the like won’t ignore the content.

Works on more than just lists!

Thing is, now it will work with ANY markup where the collapsible siblings have a common parent wrapper, not just lists.

I may turn this into a full article later in the week to go into the nitty-gritty of it and exploring all the different approaches, including the non-scripted flavor.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store