visit
After using Astro for a while, I came to realize that two of Astro's biggest features — islands and slots — both delight and frustrate me.
Astro creates an astro-island
tag, along with style
and script
tags when you include a component with client directives.
---
import Component from './components/Component.svelte'
---
<Component client:load />
If the component (with client directives) contains a slot
, Astro will create a astro-slot
tag. This astro-slot
will be found within an astro-island
.
---
import Component from './components/Component.svelte'
---
<Component client:load>
<div>Some Slotted Content</div>
</Component>
Now, if you include a component without client directives, Astro will not create astro-island
and astro-slot
tags.
---
import Component from './components/Component.svelte'
---
<Component>
<div>Some Slotted Content</div>
</Component>
Problems arise when the DOM contains astro-island
and astro-slots
. That's because these tags change the document flow, so you cannot pretend that they don't exist. To be more specific, I noticed four extremely frustrating things when astro-island
and astro-slot
are present in the DOM.
Direct descendant selectors no longer work when astro-island
and astro-slot
is in the DOM.
Imagine you want to have this HTML:
<div class="Component">
<div>Some Content</div>
</div>
But Astro creates this HTML because it has slots:
<div class="Component">
<astro-slot>
<div>Some Content</div>
</astro-slot>
</div>
If you wrote a direct descendant selector, that selector wouldn't work. That's because the direct descendant selector now targets the astro-slot
level children instead of the one you are aiming for.
/* No longer works */
.Component > div {
/* Styles here */
}
Fixing direct descendant selectors with slots is simple — all you have to do is add astro-slot
in the selector chain.
/* Works */
.Component > astro-slot > div {
/* Styles here */
}
/* Scoped CSS*/
.Component :global(> astro-slot > div) {
/* Styles here */
}
Another alternative is simply to use descend selectors instead of direct descendant selectors if the HTML structure allows for it.
/* Works */
.Component div {
/* Styles here */
}
/* Lobotomized owl selector */
* + * {
/* Your styles here */
}
.Parent > * + * {
margin-top: 1rem;
}
Unfortunately, these styles won't work on astro-island
and astro-slot
tags because they use the display: content
property.
Elements with display: contents
will have their styles ignored, so any styles added to these elements will be ignored.
The easy way to fix this is to add the styles to the elements contained in astro-island
or astro-slot
.
.Parent > * + *,
.Parent > * + :where(astro-island, astro-slot) > *:first-child {
margin-top: 1rem;
}
This one is similar to the lobotomized owl one — because Astro islands and slots use display: contents
, no styles will work on them.
These styles include grid-column
, grid-row
. So you will not be able to change the Conponent's positioning with grid-column
.
Here's an example where we laid all items out in a two-column grid. In this example, trying to use set grid-column
on the astro-island
and astro-slots
will not work.
---
import Component from './Component.svelte'
---
<div class='Grid'>
<Component client:load />
<Component client:load />
<Component client:load />
</div>
<style>
.Grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
}
/* Tries to make all grid items span the full width */
.Grid > * {
grid-column: 1 / -1;
}
</style>
The first way is to bypass astro-island
and astro-slot
with the technique mentioned above.
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
}
/* Tries to make all grid items span the full width */
.grid > *,
.grid > :where(astro-island, astro-slot) > *:first-child {
grid-column: 1 / -1;
}
The second way is to put the components in another element.
<div class="Grid">
<div><Component client:load /></div>
<div><Component client:load /></div>
<div><Component client:load /></div>
</div>
.Grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
}
.Grid > * {
grid-column: 1 / -1;
}
When Astro adds astro-island
to the DOM, they also add style
and script
tags to the DOM at the same time.
Since style
and script
tags are also considered children elements, you cannot depend on the nth-child
selector to target the right element anymore.
<div class="Grid">
<Component client:load></Component>
<Component client:load></Component>
<Component client:load></Component>
</div>
style
tagscript
tagastro-island
tags
Astro will only include style
and script
tags for the components once in
the DOM. This is why you see only one style
tag and one script
instead of
3 style
tags and 3 script
tags.
If you want to get the first component with nth-child
, you need to pass in nth-child(3)
instead of nth-child(1)
. That's because the first component is now the third element in the DOM tree.
/* Style the first component, but it's the third child */
.Grid > *:nth-child(3) > .Component {
background-color: red;
}
The first way is to wrap the components with another element.
You can then use nth-child
to style with a descendant selector to style the component.
<div class="Grid">
<div><Component client:load /></div>
<div><Component client:load /></div>
<div><Component client:load /></div>
</div>
/* Tries to make all grid items span the full width */
.Grid > *:first-child .Component {
background-color: red;
}
The second way is to stop using nth-child
and use nth-of-type
instead.
.Grid > astro-island:nth-of-type(1) > .Component {
background-color: red;
}
---
import Component from './components/Component.svelte'
import Nested from './components/Nested.svelte'
---
<Component client:load>
<Nested client:load />
</Component>
astro-island
in the top levelastro-slot
in the second level (since Component
gets content through a slot)astro-island
after astro-slot
since Nested
needs to have JavaScript functionality as well.
Most of the time, I just one layer of astro-island
and a layer of astro-slot
. So this should be an edge case more than anything else.
I've just shared with you when and how Astro creates astro-island
and astro-slot
. I've also shared with you how to overcome the styling frustrations that happen when astro-island
and astro-slot
elements are present in the DOM.