Eklavya's Blog - Web devhttps://sharmaeklavya2.github.io/blog/2020-09-13T00:00:00+05:30Making a keyboard-navigable collapsible without JavaScript2018-11-01T00:00:00+05:302020-09-13T00:00:00+05:30Eklavya Sharmatag:sharmaeklavya2.github.io,2018-11-01:/blog/css-collapsible.html<p>A 'collapsible' is content whose visibility can be toggled by clicking something. However, users without a mouse should also be able to open the collapsed content. This article explains how to make a keyboard-navigable collapsible without using JavaScript.</p><p>A 'collapsible' is content whose visibility can be toggled. Here is an example:</p>
<div class="collapsible" style="clear: both">
<label for="checkbox0" class="collapsor-lbl"> Click me </label>
<div class="focus-capturer" tabindex="0">
<input id="checkbox0" class="collapsor" type="checkbox" />
<div class="collapsible-content">
<p>This is the body of the collapsible. Its visibility can be toggled using the 'click me' button.</p>
<p>Here we will learn how to make such a collapsible without using JavaScript.</p>
</div>
</div>
</div>
<p>In this article, we'll look at how to make one.</p>
<p><strong>Important update</strong>: This article talks about a hacky solution to
make a collapsible without using JavaScript.
I recently (September 2020) found a much simpler, cleaner and semantic solution
using the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"><code><details></code></a> tag.</p>
<div class="toc"><span class="toctitle">Table of Contents</span><ul>
<li><a href="#skeleton">Skeleton</a></li>
<li><a href="#collapsibility">Collapsibility</a></li>
<li><a href="#accessibility">Accessibility</a></li>
</ul>
</div>
<h2 id="skeleton">Skeleton<a class="headerlink" href="#skeleton" title="Permanent link">¶</a></h2>
<p>Let's first create the basic structure without collapsibility.</p>
<p>HTML:</p>
<div class="codehilite"><pre><span></span><code><span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible"</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsor-lbl"</span><span class="p">></span> Click me <span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible-content"</span><span class="p">></span>
This is the body of the collapsible.
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</code></pre></div>
<p>CSS (for beautification):</p>
<div class="codehilite"><pre><span></span><code><span class="p">.</span><span class="nc">collapsible</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">max-width</span><span class="p">:</span><span class="w"> </span><span class="mi">40</span><span class="kt">em</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">black</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">border-radius</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">.</span><span class="nc">collapsor-lbl</span><span class="p">:</span><span class="nd">hover</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">background-color</span><span class="p">:</span><span class="w"> </span><span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.1</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">.</span><span class="nc">collapsor-lbl</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="w"> </span><span class="mi">1</span><span class="kt">rem</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">.</span><span class="nc">collapsible-content</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="kt">rem</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">border-top</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="kc">black</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>This is what the output looks like:</p>
<div class="collapsible">
<div class="collapsor-lbl"> Click me </div>
<div class="collapsible-content" style="display: block">
This is the body of the collapsible.
</div>
</div>
<h2 id="collapsibility">Collapsibility<a class="headerlink" href="#collapsibility" title="Permanent link">¶</a></h2>
<p>To add collapsibility, we're going to use a checkbox.
I read about it on the blog post
'<a href="https://alligator.io/css/collapsible/">Implementing A Pure CSS Collapsible</a>' by alligator.io.</p>
<ul>
<li>A checkbox maintains state about whether it's checked or not.
We can use that to maintain state about whether our collapsible has been clicked or not.</li>
<li>We can use the CSS style <code>display:none</code> to make the checkbox disappear, but still retain its functionality.</li>
<li>We can use the CSS pseudo-selector <code>:checked</code> to select a checked checkbox.</li>
<li>We will make sure that <code>.collapsible-content</code> is a sibling of the checkbox.
Then we can use the CSS sibling combinator '<code>~</code>' to select it.
When used together with <code>:checked</code> on checkbox, we can select <code>.collapsible-content</code> only when the checkbox is checked.</li>
</ul>
<p>Change the HTML to this:</p>
<div class="codehilite"><pre><span></span><code><span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible"</span><span class="p">></span>
<span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="o">=</span><span class="s">"checkbox1"</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsor"</span> <span class="na">type</span><span class="o">=</span><span class="s">"checkbox"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"checkbox1"</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsor-lbl"</span><span class="p">></span> Click me <span class="p"></</span><span class="nt">label</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible-content"</span><span class="p">></span>
This is the body of the collapsible.
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</code></pre></div>
<p>Add this CSS:</p>
<div class="codehilite"><pre><span></span><code><span class="p">.</span><span class="nc">collapsor-lbl</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">block</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">.</span><span class="nc">collapsor</span><span class="o">,</span><span class="w"> </span><span class="p">.</span><span class="nc">collapsible-content</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">none</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">.</span><span class="nc">collapsor</span><span class="p">:</span><span class="nd">checked</span><span class="w"> </span><span class="o">~</span><span class="w"> </span><span class="p">.</span><span class="nc">collapsible-content</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">block</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Output:</p>
<div class="collapsible">
<input id="checkbox1" class="collapsor" type="checkbox" />
<label for="checkbox1" class="collapsor-lbl"> Click me </label>
<div class="collapsible-content">
This is the body of the collapsible.
</div>
</div>
<h2 id="accessibility">Accessibility<a class="headerlink" href="#accessibility" title="Permanent link">¶</a></h2>
<blockquote>
<p>Web accessibility is the inclusive practice of ensuring there are no barriers that
prevent interaction with, or access to websites, by people with disabilities.
<footer><a href="https://en.wikipedia.org/wiki/Web_accessibility">Wikipedia article on Web Accessibility</a></footer></p>
</blockquote>
<p>I once had a mouse that sometimes stopped functioning, so I can feel a bit of the pain of users who cannot use a mouse.
Also, some people <em>like</em> using the keyboard for navigation and it would be bad to force them to use a mouse.</p>
<p>I don't know much about web accessibility standards and what it takes for my websites to be fully accessible,
but the least I can do is make my pages keyboard-navigable.</p>
<p>The <a href="https://alligator.io/css/collapsible/#a-note-on-accessibility">blog post by alligator.io</a>
says how to make a collapsible using only CSS, but to make it accessible they had to use JavaScript.
I, however, have a way of doing it without JavaScript.</p>
<p>When navigating a web page using the tab key, certain HTML elements have the potential of receiving focus.
This generally includes links (<code><a></code> tags) and form elements (<code><input></code> tags).
When an element receives focus, it gets the <code>:focus</code> CSS pseudo-class.
Also, that element and all its descendants get the <code>:focus-within</code> class.</p>
<p>We will therefore wrap <code>.collapsible-content</code> within a <code><div></code>.
We will make that <code><div></code> capable of receiving focus via tab by setting the attribute <code>tabindex</code> to <code>"0"</code>.
Then whenever that div has the <code>:focus-within</code> pseudo-class set,
we will set <code>display: block</code> on <code>.collapsible-content</code>.</p>
<p>Change the HTML to this:</p>
<div class="codehilite"><pre><span></span><code><span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible"</span><span class="p">></span>
<span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"checkbox2"</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsor-lbl"</span><span class="p">></span> Click me <span class="p"></</span><span class="nt">label</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"focus-capturer"</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">"0"</span><span class="p">></span>
<span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="o">=</span><span class="s">"checkbox2"</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsor"</span> <span class="na">type</span><span class="o">=</span><span class="s">"checkbox"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"collapsible-content"</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>This is the body of the collapsible.<span class="p"><</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">""</span><span class="p">></span>link1<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">""</span><span class="p">></span>link2<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">""</span><span class="p">></span>link3<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</code></pre></div>
<p>Add this CSS:</p>
<div class="codehilite"><pre><span></span><code><span class="p">.</span><span class="nc">focus-capturer</span><span class="p">:</span><span class="nd">focus-within</span><span class="w"> </span><span class="p">.</span><span class="nc">collapsible-content</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">block</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Output:</p>
<div class="collapsible">
<label for="checkbox2" class="collapsor-lbl"> Click me </label>
<div class="focus-capturer" tabindex="0">
<input id="checkbox2" class="collapsor" type="checkbox" />
<div class="collapsible-content">
<p>This is the body of the collapsible.</p>
<ul>
<li><a href="">link1</a></li>
<li><a href="">link2</a></li>
<li><a href="">link3</a></li>
</ul>
</div>
</div>
</div>
<p>Try using the tab key to navigate all the 3 links in the collapsible.
When the focus moves out of the collapsible, it closes (unless you had clicked on it to open it).</p>