min read

Using the :defined selector with slots

The :defined CSS selector allows you to target custom elements. See the MDN docs for full usage. Let's compare a couple of ways you could target custom elements:

my-element {
  display: block;
}

vs:

my-element:defined {
    display: block;
}

The difference is that the latter only targets elements once customElements.define() has been called on them.

Using with :not()

The primary use case I've found for :defined is to use it in conjuction with :not. The :not selector, as you might have guessed, let's you target falsey selectors.

Unless you inline your custom element's JavaScript in the top of the page, there will be a period of time in which the element is visible to the user, but the JavaScript defining its behavior has not yet executed. For example you might have:

<my-element>
  <span>Some content here</span>
</my-element>

This will often lead to a flash of unstyled content which is usually undesirable.

We can fix this using :not(:defined) like so:

my-element:not(:defined) {
  display: none;
}

This is the bare-bones way to prevent the flash. However this is still not an ideal solution. You would prefer that the user sees the content in this period. Of course you can do what you need here, and go as far as adding all of the styles that the custom element will add once it is defined. How far you go is up to you.

Handling named slots

Shadow DOM gives you the capability to define multiple slots, using named slots. Some times you might conditionally show a slot only under certain scenarios.

For the sake of showing what is possible, let's pretend you are creating a conditional component. This is a component that acts like an if statement in JavaScript. Using slots you define what should be shown when the condition is true and when it is false. Let's say usage is like so:

<my-conditional on>
  <span slot="true">Value is true!</span>
  <span slot="false">Value is false!</span>
</my-conditional>

The important parts of above are:

Without any styling the browser is going to show both slots before the element is defined. We obviously don't want that. As shown above, we can set the display to none to prevent the problem:

my-conditional:not(:defined) {
  display: none;
}

However, by doing this neither slot will be shown. Since we have the attribute on, we know which slot should be hidden. The fix is to add this CSS:

my-conditional:not(:defined)[on] [slot=false],
my-conditional:not(:defined):not([on]) [slot=true] {
  display: none;
}

Complex selectors like this really show the power of CSS. You can pack a lot of information that you might otherwise try to do in JavaScript.

Conclusion

That's it! I hope I've convinced you custom element authors that :not combined with :defined gives you a lot of power to do whatever you need in that small window between the page loading and JavaScript executing.