Squarespace accordion is a little bit poor when it comes to adding certain HTML elements inside the accordion item description. We can still make a certain workaround to enable support for some important HTML elements.

My most popular article on this blog till this moment is the one where I discussed how to add images in an accordion( https://www.ui-workarounds.com/how-to-make-a-squarespace-accordion-image-friendly/ ). This article came as a sequel to the older article, because I concluded people want to add all kinds of stuff in the accordion, not just text and images. So here we are:

I couldn’t extend the actual accordion block but what I was able to do, is create a “language” just like markdown, where a ** star means bolded text and *** means italic and bolded text and so on.

In our case this means that by adding a “label” surrounded by brackets in the original accordion block’s editor, our script will transpile that label into an HTML element. For example a list element can be introduced with “[[list]]” each line representing a list item. Titles could be introduced with [[ h1 ]] for h1 [[ h2 ]] for h2 and so on.

I used double brackets to avoid as much as possible the clash with user intended brackets notation with characters inside [listed elements]. Double brackets are less likely to be used by any user for their own purposes.

Adding images and videos into your accordion item description

So let’s get it straight from the beginning. The only way you can add external things to the carousel is through a link. Here is the trick actually. If you can add a link, the url doesn’t have to be just a simple domain, it can also be a path to a pdf, an image(png, jpeg etc) or even a video.

After that, the custom script will come into play(that you will add to Squarespace website footer area and that will replace the link with an image or a video tag).

The notation above will generate the html code below.

The script that you will need to add to the menu on the top -> Injections > Footer or Settings > Advanced > Code Injections > Footer – is the following script:

document.querySelectorAll('.accordion-item__description').forEach((item) => {
          const pattern = /(\[\[)([^\]\]]+)(\]\])(\s*<a[^>]*>(.*?)<\/a>|[^<]+)/g;
          const parser = new DOMParser();
          item.innerHTML = item.innerHTML.replace(pattern, (match, $1, $2, $3, $4) => {
             const tagname = $2.trim();
             const linkHTML = parser.parseFromString($4,'text/html');
             const link = linkHTML.body.children[0];
             switch(tagname) {
               case "image":
                   const image = `<img src="${link.href}" alt="${link.innerText}" />`;
                   return image;
               case "video":  
                   const video = `<video controls><source type="video/mp4" src="${link.href}"></source></video>`
                   return video;
               case "h1": 
                 return `<h1>${$4}</h1>`
               case "h2": 
                 return `<h2>${$4}</h2>`  
               case "h3": 
                 return `<h3>${$4}</h3>`   
               case "h4": 
                 return `<h4>${$4}</h4>`   
               case "h5": 
                 return `<h5>${$4}</h5>`
               case "h6": 
                 return `<h6>${$4}</h6>`   
               case "ul":
                 const items = $4.split("|").map(item => {
                     return `<li>${item}</li>`
                 const list = `
                 return list
               case "ol":
                 const listItems = $4.split("|").map(item => {
                     return `<li>${item}</li>`
                 const orderedlist = `
                 return orderedlist;
               case "table":
                 let tableString = `
                 const tableRows = $4.split(" / ");
                 tableRows.forEach(row => {
                   const tableRow = row.includes(' || ') ?  "th" : "td";
                   tableString += `<tr>`;
                   if(tableRow === "th") {
                     row.split(' || ').forEach(th => {
                       tableString += `<th>${th}</th>`
                   else {
                     row.split(' | ').forEach(td => {
                       tableString += `<td>${td}</td>`
                   tableString += `</tr>`;
                 tableString += '</table>'
                 return tableString;
               default: ''  
        item.querySelectorAll('p[style]').forEach(p => {
           if(!p.children.length) p.style.display = 'none';

After you managed to add this script, you can start with composing your content for each accordion item description. Let’s see the syntax for adding images, videos, titles, lists and tables.

[[ img ]] - this will be an image (initially you add a link towards the image)
[[ video ]] - this will be a video (initially you add a link towards the video)
[[ h1 ]] , [[ h2 ]] , [[ h3 ]] , [[ h4 ]] , [[ h5 ]] , [[ h6 ]] - 6 level of title
[[ ul ]] - unordered list(bulleted list)
[[ ol ]] - ordered list(numbered list)
[[ table ]] - tables

Disclaimer: ul and ol list items are separated by | character and the table header row items are separated by || and normal body row items are separated by |

“Ok, man, but give me the code for this example!!”.

Sure ! Here it is:

Now that you know how to add the script and the content, let me explain how the script works. If you are interested in the coding part, read further.

  1. First step is to iterate through all accordion item descriptions with querySelectorAll and forEach.
  2. We create a regular expression to match [[ tagname ]] a link or some text content after it
  3. In the third step we replace the accordion item description’s HTML with the replaced HTML. The regular expression is grouped by the () and we have 4 groups. $1 and $3 represents the [[ and ]]. The $2 is the actual tagname between these brackets. The $4 is what comes after the brackets. This can be a link(for images and videos) or text content.
  4. In the tagname constant we remove spaces around the tagname and for the actual content which can be also a link HTML tag, we turn the string back to an html with DomParser().

5. Now we need to filter the regular expression’s results and handle each result with a different action.

  • For image we replace the link with an image tag that has the href of the link as the image’s source and the links content for the images alternative text
  • For video we add only one source from the links href attribute since nowadays browsers support most of the video formats, so no need for multiple sources. The video comes only with one attribute, the controls.
  • For titles (h1-h6) we wrap our text content with the h1-h6 tag.
  • For the unordered list(ul) we split the text content at the | character and return <li> tags with the splitted text, wrapped in a <ul> tag
  • The same is valid for the ordered lists(ol)

In the case of the table we have some special characters with special meanings.

  • the / means that a new row is comming
  • the || means that this is a table head row with th cells
  • the | means that this is a normal table body row with td cells

Then the tr tags are wrapped in a table tag and that’s it.

In the end we do a little cleanup. Since the contents are replaced in paragraph tags that Squarespace adds and since HTML can not hold lists, tables etc in paragraphs, it closes the paragraphs without the content inside, so they end up empty <p> tags with some space inside, nothing else. So I do another iteration through p tags that have a style attribute on them and check if they are empty(no html children tags inside) and then I hide them.

Now a last thought that I want to share: All these images, videos , tables and lists can be styled with CSS in the custom CSS panel on the top(near the Injections link). You just have to target the correct class name for the accordion description:

.accordion-block .accordion-item__description {
    table {
       width: 100%;
    img {
      max-width: 100%;
    video {
      width: 100%; 

If you still have questions about anything, please write a comment, let’s get in touch. I always answer all questions. Or you can watch also the video with the same solution:

Special thanks to pixabay.com for the great and free images that I used in my blog.

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 5

No votes so far! Be the first to rate this post.

Categorized in: