Web Components and You (part 9): Handling empty slots
Published 2024-08-12, About 4 minute read.
You won't go very far building web components until you reach an interesting scenario: How do you tell when a slot has stuff in it?
Here's an example:
Yes, you could say this is the user's fault. They provided both a slot and attribute. But can't we be more liberal in what we expect from others and help guard against these cases?
In order to do that we would need to know if there is content in the slot. Basically, if both are supplied, we should defer to the slot
The slotchange
event
We have a tool from the shadow DOM api: the slotchange event. The event is triggered when content is placed in the slot, and when those immediate nodes in the slot changes. (If you change the great-great grandchild nodes of the slot elements, no event fires)
So let's see this in action!
Check out the console, and you'll see an event logged out, and something
called assignedNodes()
logged as well:
Without doing anything except placing a span in this slot, we see that the slotchange event was triggered. The way I think of it is that the following happens in order:
- The component is constructed
- The component is connected with the DOM
- The span element is rendered inside the "light dom" of our component
- The span is reflected into the slot in the shadow DOM
- The event
slotchange
is triggered
I can't promise this is what's happening under the hood, but it makes a pretty dang good mental model. And it also "explains" why the slotchange fires even though we haven't dynamically changed anything.
The other cool thing here is that the "assigned nodes" are basically all the o nodes reflected into that slot. So, it's easy enough for us to check what we need in that slot!
So how do we "check" the contents and adjust?
The strategy is straightforward once you know how to inspect that slot. We're
going to make a reactive state property hasLabelContents
, and if
there's stuff in the slot we record it in that state. Then, we render out what
we need to with logic pertaining to that state.
Here we go!
There we have it. The component checks the slot, toggles a state property, and you can decide what to render from there
But there are some gotchas...
Unfortunately, you can do slots conditionally. Say, for example, you wanted to show a slot only if there are items in that slot. Well, you enter a kind of catch 22 there. If the slot is not on the DOM the slotchange won't happen. But even if you try to default showing the slot to trigger the event to not show the slot, you'll see that it doesn't work:
You might think that the second instance would render the slot, the slotchange event would trigger, the slot would appear empty, and then the alternate value of the ternary would show. Sadly, this just isn't the case. In the second instance the slotchange event never fires. And I think this is because there is never content reflected into the slot.
There's a workaround, but I don't recommend it. If you use
firstUpdated
and check the slot contents in that lifecycle
method, you can correctly render what you want with a ternary, but
only on the first render. If state changes or a
slot gets added, it may or may not update correctly depending on the
situation.
That's all for now!
Hopefully that helps answer how to detect when a slot is empty. Add a comment below or find me on Bluesky or Mastadon. I also have an RSS feed here