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:
<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;
break;
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>`
}).join('');
const list = `
<ul>
${items}
</ul>
`
return list
case "ol":
const listItems = $4.split("|").map(item => {
return `<li>${item}</li>`
}).join('');
const orderedlist = `
<ol>
${listItems}
</ol>
`
return orderedlist;
case "table":
let tableString = `
<table>
`
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';
});
});
</script>
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.

- First step is to iterate through all accordion item descriptions with querySelectorAll and forEach.
- We create a regular expression to match [[ tagname ]] a link or some text content after it
- 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.
- 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.
Hi,
This is awesome, thanks for the post.
//I added the [[ p ]] – because most aswers I want as body text, given my h1-h6 is a different font. just added below, so easy
case “p”:
return `${$4}`
Hi. I am not sure what you mean, but if you want just a p tag you don’t have to put the [[ p ]] notation, just add a simple text and paragraph will be created around that text by default.
Hi! I am trying to finish up a website and I used your code video to add images to an accordion, but I cannot get the images to resize (especially on mobile!). I tried using the code you suggested in another thread:
.accordion-item__description img {
max-width: 100%;
}
but no luck.
Any tips would be amazing!
Hi. It would be hard to give you the best solution without visiting your website but I will try to answer this.
First solution:
.accordion-item__description img {
max-width: 100%!important;
}
Second solution:
.accordion-item__description img {
min-width: 0!important;
max-width: 100%!important;
}
Third solution:
Please check if the image is inside the .accordion-item__description class. Perhaps the class changed in the meantime in a newer versions of Squarespace. They are keep updating stuff so this is also an option.
Fourth solution:
You have to add this CSS ode in the Custom CSS panel on top left corner. If you add it somewhere else please wrap it in a <style> </style> tag.
Hi! I’m having issues with the video inside the accordion. I am try to link to a Vimeo video but it will not load. Is there specific code to show videos that are hosted on Vimeo or YouTube?
Hi. Well in this script I did not include embedded videos only uploaded ones.
If you are using only images and/or videos in accordion, then you can just use the other version of the script:
https://www.ui-workarounds.com/mediaimagevideo-friendly-squarespace-accordion/
Use the embed URL for the VIMEO videos that has this format: https://player.vimeo.com/video/831583044?h=ba2022dae0
Don’t use the normal url for the Vimeo video, but the embedded url from the Share > Iframe source attribute.
I would like to add a widget that shows reviews from yelp in the accordion as well as a call to action button that is linked to another page. How might I go about these additions?
I am using this for a overview offerings page that will take users to the landing pages for each offer
Well, it’s not currently implemented. What I could do is make another improvement for anyone to be able to introduce html of any sort inside the accordion items.
Can you put images in the table? It doesn’t seem to work for me.
Not yet, but will consider it in the near future!