DEV Community

Cover image for Learn how to isolate CSS with Shadow DOM and Slots in a gallery web component (Learn Modulo.js - Part 5 of 10)
michaelb
michaelb

Posted on

Learn how to isolate CSS with Shadow DOM and Slots in a gallery web component (Learn Modulo.js - Part 5 of 10)

👋 Hey all, welcome back this week! Each tutorial is self-contained, so feel free to start at the beginning, or just plunge in here.

Introducing: The PhotoGallery Component

Demonstrating code for vanishing component with images and DOM structure

Our task in this tutorial will be to build a Photo Gallery component, that can take any number of image tags and display them. Our goal is to make this convenient: HTML developers should be able to immediately know how to use it and use it effectively.

Starting snippet

In this tutorial, we will start with Props, just like with the PictureFrame component in Part 2.

As with previous tutorials, you can simply copy and paste this code into any HTML file to start:

<template Modulo>
    <Component name="PhotoGallery">
        <Props
             image1
             image2
        ></Props>
        <Template>
           <div class="flex-gallery">
               {{ props.image1|safe }}
               {{ props.image2|safe }}
           </div>
        </Template>
        <Style>
            .flex-gallery {
                border: 2px ridge gray;
                padding: 10px;
                display: flex;
                flex-wrap: wrap;
            }
            img {
                border: 2px ridge goldenrod;
                margin: 10px;
                border-radius: 10px;
            }
        </Style>
    </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<x-PhotoGallery
    image1="<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Helen_KellerA.jpg/193px-Helen_KellerA.jpg' title='Hellen Keller' />"
    image2="<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Mark_Twain_1907.jpg/187px-Mark_Twain_1907.jpg' title='Mark Twain' />"
></x-PhotoGallery>
Enter fullscreen mode Exit fullscreen mode

One important new filter we added is the |safe filter. This tells Modulo to interpret the variable as HTML (not text), and thus correctly render the image. Otherwise, the user would see the code for the images, not the image itself.

Introducing Part 5: slots

As of right now, there are two glaring issues with our code:

  1. We only support 2 images. If we wanted more images, we'd have to manually define them in Props. If we wanted dozens (or even hundreds) of images, it would become very quickly unmanageable.

  2. Putting HTML into quotes as HTML attributes (Props) is really messy. We need to watch out for quotations, and the code quickly becomes hard to manage.

The <slot></slot> tag solves both of this. It allows you to use the content of the HTML tag, allowing you to write HTML much more naturally. In Part 5, we'll get practice with Slots, but also learn about the related ShadowDOM, and about CSS isolation in general.

Slots allow there to be "empty spots" in your HTML that proceed to get filled by arbitrary content supplied when your component is used. Adding a slot is as simple as including a <slot></slot> HTML element in your component's Template definition. To fill up a slot with HTML content, add the content between the opening and closing tags of your HTML element.

Step 1: Adding a slot

The quickest thing to do is just replacing the div tag with the special slot tag:

<Component name="PhotoGallery">
    <Template>
       <slot class="flex-gallery"></slot>
    </Template>
    <Style>/* ... */</Style>
</Component>
Enter fullscreen mode Exit fullscreen mode

Then, to use it, we do the following:

<x-PhotoGallery>
    <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Helen_KellerA.jpg/193px-Helen_KellerA.jpg' title='Hellen Keller' />
    <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Mark_Twain_1907.jpg/187px-Mark_Twain_1907.jpg' title='Mark Twain' />
</x-PhotoGallery>
Enter fullscreen mode Exit fullscreen mode

Step 2: Adding a default

To make our PhotoGallery a bit more useful to whoever might use it, let's add a default appearance, so "empty" photo galleries have a message:

<Template>
   <slot class="flex-gallery">
       <p><em>No photos found.</em></p>
   </slot>
</Template>
Enter fullscreen mode Exit fullscreen mode

To test an empty gallery, write the tag without any space between the opening and closing: <x-PhotoGallery></x-PhotoGallery>

Aside: Named slots

While not used here, it's good to know you can have multiple slots by giving them names (with name=), and specifying the name with the special slot= attribute. For example: <slot name="sidebar-photos"></slot>, and <img slot="sidebar-photos" /> would create a new, separate slot from the default, nameless slot, which can have it's own default and it's own content.

CSS Isolation: Enter the Shadow

Our <x-PhotoGallery> component is complete at this point, for certain purposes. However, let's imagine this is in a larger page, and that existing CSS in that page is interfering with our styling. This is the use for CSS isolation. Let's start with practicing "shadow", set on the Component itself, as such: <Component mode="shadow">

When shadowDOM is activated, then the component switches to
using a somewhat new feature of HTML: the Shadow DOM. This dramatically named feature -- sounds kind of like a super villain -- allows a Web Component to create a protected or hidden DOM fragment (e.g. portion of an HTML page) that can be manipulated, while "protecting it" from outside CSS. This will dramatically change how the component renders, since the browser will treat it quite differently, and all of it's children will be "removed" from the DOM in the hidden Shadow DOM, making it appear "empty" when you right click and do Inspect (but with more clicking, you can find the "shadow root" where the hidden content is).

When a component is in shadow mode, and you launch your site or build your site, it's CSS will NOT be put into the main CSS file. That means that it will be stored in the JavaScript file and only added whenever that component appears on the screen. Furthermore, due to the what the browser works, the CSS will be fully isolated in both directions: Outside CSS won't affect your component's children, and inner CSS is incapable of affecting anything else, and won't even be added until the component gets on the screen. Since it's fully isolated, there is no need to prefix, so CSS will be inserted as is.

Step 3: Entering the Shadow DOM

Showing shadow root version of photo gallery

Examine the image above. Do you see how it shows a "shadow-root"? That's what we want. Let's "switch on" Shadow mode for our component:

<Component name="PhotoGallery" mode="shadow">
Enter fullscreen mode Exit fullscreen mode

Check out what happens. The first thing you will notice is the images no longer get the border. This is intentional: The Component's CSS will not affect it's slots, since they are "protected" by the shadow DOM. This means we'll need to move the img CSS to be global, regular / global CSS:

<x-PhotoGallery>...</x-PhotoGallery>
<style>
    img {
        border: 2px ridge goldenrod;
        margin: 10px;
        border-radius: 10px;
    }
</style>
Enter fullscreen mode Exit fullscreen mode

However, if we have an image in the template itself (that is, not using slots), then it will get the internal style. Let's add a decorative camera that floats to the right:

<Template>
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Studijskifotoaparat.JPG/312px-Studijskifotoaparat.JPG" />
    <slot class="flex-gallery"><p><em>No photos found.</em></p></slot>
</Template>
<Style>
    img { float: right }
</Style>
Enter fullscreen mode Exit fullscreen mode

Note that this does not get the global style, but does get the component style.

Step 4: Vanishing act

Finally, let's practice one more component mode: vanish. Note the image at the top of this tutorial doesn't show any <x-PhotoGallery> component. That's because it vanished!

This will not use the ShadowDOM, but still provide partial isolation: Outer CSS will influence inside the component, but the component's CSS will remain isolated and not influence slots. More importantly, the component will "vanish" -- that is, it will remove itself when it is displayed, leaving no trace of the web component, and a simpler / potentially more performant DOM structure.

This is done just like mode="shadow":

<Component name="PhotoGallery" mode="vanish">
Enter fullscreen mode Exit fullscreen mode

<x-PhotoGallery> - Complete Example

Finally, we put everything together, and we end up with the final results. Feel free to copy and paste the following to try it out:

<template Modulo>
    <Component name="PhotoGallery" mode="vanish">
        <Template>
            <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Studijskifotoaparat.JPG/312px-Studijskifotoaparat.JPG" />
            <slot class="flex-gallery"><p><em>No photos found.</em></p></slot>
        </Template>
        <Style>
            .flex-gallery {
                border: 2px ridge gray;
                padding: 10px;
                display: flex;
                flex-wrap: wrap;
            }
            img {
                float: right;
            }
        </Style>
    </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<style>
    img {
        border: 2px ridge goldenrod;
        margin: 10px;
        border-radius: 10px;
    }
</style>
<x-PhotoGallery>
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Helen_KellerA.jpg/193px-Helen_KellerA.jpg" title="Hellen Keller" />
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Mark_Twain_1907.jpg/187px-Mark_Twain_1907.jpg" title="Mark Twain" />
</x-PhotoGallery>
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this section, we practice using slots to make more user-friendly components, and learned how we can use shadow and vanish rendering modes to isolate our components in different ways. Consider these all to be "tools" for your web component "toolbox", and very handy when dealing with conflicting CSS. In the next tutorial we'll be going deeper with Template, so be sure to follow to catch how to use {% for %}, {% if %} and other control-flow template-tags!

Top comments (0)