If you’ve spent some time in Astro, you’re probably familiar with the <slot />
feature which you can use to render child components inside a wrapper component. In this post, we’ll explore a way to modify Astro slot children from inside the wrapper component.
Astro Slot Basics Link to this heading
You can learn all about Astro slots in the Astro docs, but for this post, we’ll start with a basic example which we’ll build upon later.
Let’s say we want to build an FAQ section with some custom styles and behavior using the HTML <details>
element. We might want one component for the FAQ section and one component for the individual FAQ items that go inside the section.
First, we’ve got an Astro component for the FAQ section where we’ll insert our frequently asked questions via the <slot />
.
Next, we’ve got an Astro component for each individual FAQ item. This component uses the HTML <details>
and <summary>
elements together to enable each answer to expand and collapse.
Putting those two components together on our Astro page, we insert the FAQItem
components between the opening and closing tags of our FAQSection
.
In the final rendered HTML shown below, the <slot />
placeholder inside the FAQSection
component gets replaced with the rendered HTML of our FAQItem
children components:
This would display on the page as something like this:
Frequently Asked Questions
How many strawberries do you have?
Would you give me a strawberry, please?
(I’ve added some additional styles, not shown.)
Astro slots are very useful!
Limitations of Astro Slots Link to this heading
This is all pretty standard stuff for Astro and other web frameworks like React, Svelte, etc., but one of the limitations of Astro slots as compared to React children is there is no built-in way to modify children passed in via the <slot />
. Suppose we want to set an attribute on certain children, or style odd-numbered items a little differently. By default, we can’t do that because Astro doesn’t give us access to pre-rendered slots content.
Modify Astro Slot Contents Link to this heading
Even though we don’t get a nice children
array in Astro like we do in other frameworks, Astro does have a slots
property on the Astro
global with a render
function that allows us to grab the rendered HTML of the <slot />
contents. Combined with Astro’s set:html
template directive on an Astro <Fragment />
, and an HTML parser library, we should have enough tools to achieve our goals.
Here are the steps we’ll take:
- Grab the slot HTML contents using
Astro.slots.render()
- Parse the HTML into a DOM tree using a parser like linkedom
- Iterate over our slot children via the DOM tree and process however we want
- Render the modified slot children using
<Fragment set:html={modifiedSlotHTML}/>
Grab The Astro Slot Contents Link to this heading
To get the HTML of the slot contents, we’ll use Astro.slots.render('default')
. We’ll await the render function and assign it to a variable in the component script 1 part of our Astro component.
Parse HTML Link to this heading
To parse HTML, we’ll need to install an HTML parser. I found linkedom to work pretty well for this, but there are several other options available if you don’t want to write your own HTML parser.
As shown below:
- First, import
parseHTML
from the linkedom package - Then we destructure the parsed HTML document into
document
fromparseHTML(html)
- Finally, assign the HTMLCollection
document.children
to achildren
variable
Modify Slot HTML Link to this heading
Now we have have an array of children HTML elements that were passed into the <slot />
of the FAQSection
component. We can do almost anything we’d normally do to HTML elements.
There are a few important things to get clear:
-
children
consists of HTML elements, not Astro components. This means you cannot pass props to the children and access them via the child componentAstro.props
. Unfortunate! - DOM manipulation on
children
is implemented by our HTML parser (linkedom in this case), not the browser. Remember, everything between the code fences (---
) in Astro runs on the server or build process, not in the browser. HTML parsers like linkedom may or may not implement all the DOM manipulation methods you’re used to using in the browser.
So how should we modify our FAQ section slot children? One common pattern for FAQs is to have the first one expanded by default. Since we’re expecting our children to be FAQItem
components 2 which render a <details>
element, we can add the open
attribute to the first slot child.
Render Modified Slot Contents Link to this heading
Finally, we need to replace the <slot />
placeholder with a <Fragment>
and set the HTML of the fragment to our modified document using Astro’s set:html
client directive.
And here’s the result! Notice the first FAQ is open by default, which is exactly what we wanted.
Frequently Asked Questions
How many strawberries do you have?
Would you give me a strawberry, please?
Let’s Wrap Up Link to this heading
Thanks for reading! In summary, you can modify astro slot contents, but it comes with some pretty big caveats:
- You need to use a third-party HTML parser (or implement your own 😎).
- You can’t pass props to slot children which are Astro components. I know, bummer! 3
If you have any feedback, questions, or ideas about this post, I’d love to hear from you. Eventually I’d like to implement some form of comments on this blog, but for now you can email me here: cassidy@cassidysmith.dev