skip to content

JavaScript: HTML content that expands on click

A very common requirement these days is wherever you have large blocks of text to hide most of it from the user until they click on a button or link to see more.

Expandable content example

This is the first paragraph of text that will always be visible. A link will be appended to this paragraph with an option to show more content. Actually, anywhere in the paragraph can be clicked.

The second and subsequent paragraphs (or lists or other elements) will remain in the HTML, but hidden until requested.

When the link is clicked, JavaScript simply removes the truncated class from the parent container, which has the effect of removing the More… link and displaying all of the content normally on the page.

The only gotcha is that the first child of the container must be a P.

See below for an example where the state can be toggled.

HTML markup

We are using a DIV container with a class of truncated: <div class="truncated"> <p>... text to be visible on page load ...</p> <p>...</p> <p>...</p> <p>...</p> </div>

This allows us to have as many collapsed text sections as we want on a single page.

CSS for this example

We hide all children of our DIV except for the first P element which is displayed with a link appended using ::after using the following CSS styles:

<style> .truncated > * { display: none; } .truncated > p:first-child { display: block; cursor: pointer; } .truncated > p:first-child::after { content: "\00a0 More…"; } </style>

JavaScript code for this example

The following code should be set to run after the page has finished loading (i.e. following the DOMContentLoaded event):

document.querySelectorAll(".truncated").forEach(function(current) { current.addEventListener("click", function(e) { current.classList.remove("truncated"); }, false); });

This will use any click on a container with a class of truncated as a trigger for displaying the hidden content from that container and at the same time removing the More… link from the first paragraph.

Collapsible content with toggle

Similar to our first example, but this time it goes both ways:

This is the first paragraph of text that will always be visible. A clickable element will be appended to the bottom of the container to show/hide more content. Alternatively, you could make the entire container clickable, but for now we're gone with the toggler.

The second and subsequent paragraphs (or lists or other elements) will remain in the HTML, but hidden until requested.

When the toggler is clicked, JavaScript toggles an open class on the parent container, which in turn shows and hides the additional content.

Again, the only gotcha is that the first child of the container must be a P.

HTML markup

We are using a DIV container with a class of collapsible: <div class="collapsible"> <p>... text to be visible on page load ...</p> <p>...</p> <p>...</p> <p>...</p> </div>

CSS for this example

A bit more complicated than the previous example as we have to absolutely position the toggler within the container, and the content needs to change according to the state of the parent (open or closed):

<style> .collapsible { position: relative; padding-bottom: 0.5em; } .collapsible:not(.open) > * { display: none; } .collapsible:not(.open) > p:first-child { display: block; } .collapsible > .toggler { position: absolute; left: 0; bottom: 0; display: block; width: 100%; background: #fff; text-align: center; cursor: pointer; } .collapsible > .toggler::after { content: "\25bc"; } .collapsible.open > .toggler::after { content: "\25b2"; } </style>

For the toggler labels we are using Unicode characters &#x25bc; and &#x25b2;

JavaScript code for this example

Our JavaScript code now has to append the toggler as a new child element of the container and make it clickable:

document.querySelectorAll(".collapsible").forEach(function(current) { let toggler = document.createElement("div"); toggler.className = "toggler"; current.appendChild(toggler); toggler.addEventListener("click", function(e) { current.classList.toggle("open"); }, false); });

The main difference here is that it's the new toggler element that becomes clickable, updating the container classList.

Before you all ask, there is no simple CSS way to smoothly transition the container height as CSS can't transition between unknown values. Read below for a JavaScript approach.

Collapsible with transition

Here the functionality is the same as before, only the container height will change using a smooth CSS transition rather than an abrupt jump:

This is the first paragraph of text that will always be visible. A clickable element will be appended to the bottom of the container to show/hide more content. Alternatively, you could make the entire container clickable, but for now we're gone with the toggler.

The second and subsequent paragraphs (or lists or other elements) will remain in the HTML, but hidden until requested.

When the toggler is clicked, JavaScript toggles an open class on the parent container, which in turn shows and hides the additional content.

Again, the only gotcha is that the first child of the container must be a P.

What we do is get a measurement of the container height in pixels both with and without the extra content displayed, and then transition between those two heights. Those heights are stored as data-* attributes.

<style> .collapsible.slow { position: relative; overflow: hidden; padding-bottom: 0.5em; transition: height 0.5s ease-out; } .collapsible.slow > * { display: none; } .collapsible.slow > p:first-child, .collapsible.slow.open > *, .collapsible.slow.ready > * { display: revert; } .collapsible.slow > .toggler { position: absolute; left: 0; bottom: 0; width: 100%; background: #fff; text-align: center; cursor: pointer; } .collapsible.slow > .toggler::after { content: "\25bc"; } .collapsible.slow.open > .toggler::after { content: "\25b2"; } </style>

The revert value for display restores the container children back to their original state rather than setting them to block which would break some layouts.

<script> // Original JavaScript code by Chirp Internet: chirpinternet.eu // Please acknowledge use of this code by including this header. const getContainerHeight = (el) => window.getComputedStyle(el).getPropertyValue("height"); document.querySelectorAll(".collapsible.slow").forEach(current => { let toggler = document.createElement("div"); toggler.className = "toggler"; current.appendChild(toggler); current.dataset.initial = getContainerHeight(current); current.classList.add("open"); current.dataset.final = getContainerHeight(current); current.classList.remove("open"); current.style.height = current.dataset.initial; current.classList.add("ready"); toggler.addEventListener("click", (e) => { current.style.height = current.classList.toggle("open") ? current.dataset.final : current.dataset.initial; }, false); }); </script>

In the HTML you will see the container change from its iniital state:

<div class="collapsible slow">...</div>

To an 'initialised' state:

<div class="collapsible slow ready" data-initial="143px" data-final="334px" style="height: 143px;">...</div>

Which then toggles to an 'open' state:

<div class="collapsible slow ready open" data-initial="143px" data-final="334px" style="height: 334px;">...</div>

Note that we've used some ES6 syntax here, such as let, const, dataset and arrow functions, as well as display: revert;, none of which are properly supported by Internet Explorer or other older browsers.

Continue to our new article where we present a new version of the above code which also includes built-in support for browser resizing and device rotation.

References

< JavaScript

Post your comment or question
top