Ten Things On Using Webcomponents With Elm

by Tim Case

I’ve found web components are a game changer when it comes to building user interfaces. I’ve written about them previously regarding the benefits of using them no matter what your framework happens to be but because they offer another way to do javascript interop I think it’s worth discussing some of the special benefits they bring when combined with Elm.

What I like most is that I can use the javascript I already know to quickly create the widgets that make up the majority of an app. It took me a lot of learning, prototyping, and breaking things (Elm’s Virtual DOM) to finally figure out what you can and can’t do with web components, this article is a list of observations and things I wish I knew before I got started.

  1. Use the guide and read it

    The first thing you should do is to check out “A Guide to Using Elm With Webcomponents”. This excellent guide touches on every subject that will come up when using web components, I wish it existed when I first got started. Pay close attention to the section “Gotchas”, because chances are good that you are going to break the Elm’s virtual DOM at some point, my advice is that if this happens it’s usually a sign that whatever custom element caused the breakage might be better off being broken down into a smaller components. Web components are for leaf nodes, not containers.

  2. You don't need to use Shadow Dom or Template tag.

    Most articles about web components really like to extol the virtues of shadow dom and kind of make it sound like the magic sauce that makes web components tick. I mean who doesn’t like total isolation as a virtue?

    The problem with shadow dom is that it will block all external CSS styling meaning you can’t use CSS frameworks like Bootstrap or TailwindCSS.

    However…

    **Not using Shadow dom means not being able to use . Slot is only available to components that use Shadow dom**.

    There is one exception to this. My understanding is that StencilJS which is a framework for creating web components will allow the use of slot without using Shadow dom, just understand that this is not the browser standard and is something specific to StencilJS.

    Shadow dom becomes more useful the larger the developer audience a web component is designed for. If you’re on a small team working exclusively on an Elm app then you probably don’t need to Shadow dom. If you are building web components for use across an enterprise then it depends on how big the enterprise is, if you’re a small startup then probably not, if you are Saleforce then you probably want Shadow dom. If you are building web components to be used by developers outside your organization then you probably want to use Shadow dom, a good example is Google developing Material Design web components.

  3. Web components that wrap a lot of functionality like a form probably will probably cause Elm Virtual Dom errors

One of my first experiements with web components was to see if I could pull off creating a login form component that I could use for both a Rails and an Elm app. I got it working with the Rails app no problem but with Elm there was Virtual DOM errors because I believe because the login form’s child nodes were being added to and removed.

```HTML </my-login-form ```
Web components with a lot child nodes like this login form gave me Elm Virtual DOM errors</figcaption </figure> My rule of thumb is that if I run into VDOM errors with a web component it's a sign that the component is doing too much and that I should decompose into smaller pieces. For the login form it meant abandoning the entire form as a webcomponent idea and switching instead to creating the input fields and buttons as components.
How web components was originally envisioned was as leaf-node or light-DOM-using components, of the sort HTML already has. For example, <dialog>, <details>, <input type=range>, etc. Web components was supposed to give you the tools to create your own components of this sort, such as <tabpanel>, <toast>, <carousel>, <switch>, etc.
Domenic Denicola, author and editor of the modern custom elements spec, in a Hacker News comment thread
5.

Should you use a web component framework? Maybe, maybe not.

Popular frameworks for creating web components include Lit by Google, Catalyst by Github, and Stencil by Ionic. You don't need to use a framework to use web components and it might be better in the beginning not to use one because web components are not really that complicated. I didn't use a framework and quickly I noticed some repetition in my web component classes and so it was natural to extract out common functionality into an abstract base class. After creating a few components I noticed that I was also creating my own framework for web components and what I notice is that my own custom framework does pretty much the same thing as the frameworks listed previously,. Now I'm not saying don't use a framework quite the contrary I think a framework is helpful especially when it comes to avoiding bikeshedding, providing docs for new developers, and as a helper to get started faster, but I do advocate learning vanilla web components first and framework second rather than learning web components via a framework 6.

I tried to roll my own Slot functionality and it wasn't successful.

As mentioned previously, a web component must use shadowdom in order to use slot. I did not want to use Shadow dom with my web components because my development process is heavily dependent on using TailwindCSS and external CSS framework won't work with Shadow Dom. I tried to roll my own slot functionality where I tried to run a replace function on and it ended up breaking Elm's virtual dom in many cases. Not alway it sometimes worked but it was never really reliable. This is a part of the article where I'm more describing my own experience rather than offering advice, it's quite possible works just fine when you use Shadow dom, and also there is a way to use with Stencil components that doesn't require also using Shadow dom and maybe that also works just fine, so I guess in the off chance that you decide to roll your own Slot I had limited success with it. 7.

When to use a custom element and when to use a port?

Custom elements and ports both allow interop with javascript external to Elm and so a good question is when should you use a custom element and when should you use a port? Out in the wild I've noticed there are two different concepts of web components where one is about creating leaf nodes that are natural extensions of HTMLElements and the other is the ability to execute any arbitrary javascript by inserting a node into the DOM. It's this latter use of a web component that makes it conceivable that web components could be used to entirely replace ports, I mean this is a ridiculous strawman argument I'm making but I want to ask the question, "Why not replace all ports with a custom element?" A Port requires a lot of set up and glue code in Elm that a custom element doesn't so there's an argument to make that custom elements are much less work to use. Using a custom element is about being able to use javascript instead of Elm and I think the reason for doing this is to have access to 3rd party components and libraries as well the convenience of not having to build something in Elm. The drawback is that each piece of outside JS you bring into an Elm app brings you further and further away from safe runtime execution. Javascript interop always involves a trade off of this type. For me using custom elements to extend an input control is acceptable interop as my experience is that yes run time errors are now going to happen but they will be small and easy to identify and fix. Ports create a border where certain expectations must be met for javascript interop to happen, and using custom elements to circumvent those enforcements feels a little bit on the hinkey side of things for me. I personally don't like using custom elements for anything that doesn't look like it would fit as an extension of HTMLElement, especially if it's something that doesn't affect the DOM visually in anyway. I can't say this is the rule everyone should follow and I can't even say if this is a compelling reason for not doing things that could be done with a port but I know that I advocate for small specific uses of custom element and that all other things are gonna be a port for me. You might disagree and I can't really argue that strongly against it. 8.

Don't use built in components

**Do not use custom built-in elements** because [the webkit team at Apple have flat out said they will not support it](https://github.com/WICG/webcomponents/issues/509#issuecomment-224212371). Don't make the mistake I did and have to deal with debugging why something is only broken in IOS Safari, trust me you do not want to experience that pain. Here's what a customized built-in element looks like ```javascript class ExpandingList extends HTMLUListElement { constructor() { super(); ... } } customElements.define('expanding-list', ExpandingList, { extends: "ul" }); ``` Here's what it looks like when in an HTML declaration ```html
    ``` 9.

    I used mustache for templating and I'm happy I did.

    Custom elements makes no specification about how you inject html, it can be as simple as assigning a string like '
    ' to innerHTML. The web component spec has a content template element which seems like it should somehow work with the custom element but that's more about rendering a template in the DOM along side a custom element, something I never really found use for. Most web component frameworks provide templating that looks like a close cousin to JSX. I never really liked JSX because I prefer having markup in a separate file where my editor can color code the syntax and there are no exceptions for things like having to use "className" instead of "class". I found Mustache templating to be an excellent tool for using with web components because it provides a nice set of functionality in the form of rendering variables, if blocks, loop blocks, and partials. 10.

    Attributes and not properties

    The ["Guide to Using Elm Elm With Webcomponents"] does a great job showing how Elm can send data to a web component via an html attribute embedded as part of the markup for a web component, and also via properties where getters and setters are set by Elm via javascript function calls on the web component object. It then follows up with a nice section ["Attributes vs. Properties"](https://github.com/elm-community/js-integration-examples/tree/master/more/webcomponents#attributes-vs-properties) that has the following recommendation: > Use properties unless you want your custom elements to be used from hand-written or server-rendered HTML. > The reasoning being > You're interacting with your custom element via JavaScript anyways, so the fact that properties can not be set from raw HTML is usually not an issue > You can transfer structured data via properties, not just strings > It's easier to use a consistent interaction method with custom elements from Elm - just use Html.Attributes.property everywhere > On the other hand writing custom elements using only attributes might be more suitable for your use case as they can easily be included in static HTML, hand-written or produced by server-side-rendering. I'm going to slightly contradict this advice by suggesting use attributes and not properties. The reason being - It's more likely than not that you are going to want to use your web component outside of Elm even if you think right now you don't. - Using attributes as part of your markup makes it easy to see what's going into your web components by inspecting the elements through browser dev tools. Now of course there are drawbacks, using properties does mean you'll be passing everything as strings and this gets awkward for things more complex than primitives such as object or arrays. It can make the markup ugly but it's definitely doable. The decision whether to use attributes or properties is not one of monumental consequence they both do the same thing in a slightly different manner. I think the most important decision to make is that irrespective of the attributes vs properties question, choose one and stick with that one only. Don't mix attributes with properties. My decision to use web components was initially as a way to develop UI strictly for my Elm app. I used attributes because I wasn't familiar with using properties in Elm and I'm glad I went that route because later I ended up using the same web components in a RubyonRails server rendered app and in a Wordpress blog. I later found out that portability of web components across different frameworks both client side and server side is one of the big benefits to web components and so I recommend going with attributes so that you have this option open. 11.

    Web component to Elm communication is via Events.

    The copy-to-clipboard example in the guide shows a web component listening to events in it's DOM children and then dispatching events that are caught by Elm passing along arbitrary than can be decoded by Elm. Where attributes and observedAttribute is the communication mechanism of Elm to a web component, events are how the web component communicates back to Elm. I recommend studying the example because it lays out very clearly a communication route that was hard for me to figure out on my own. Web components are a great tool to use when building user interfaces for Elm apps, and it helps to know what the intricacies of it are. The guide to using web components is the place to get started. Frameworks for creating web components exists but you might not need one, there's benefits to rolling your own. You don't need to use all the pieces of the web component API, specifically Shadow Dom and templates. Avoid creating web component mini apps where a component might represent an entire form, instead create small components that might represent an individual input within the form. Using slot can be tricky with Elm because it might change Elm's understanding of what's in the DOM. Custom elements allow the injection of any arbitrary javascript by insert an element in the DOM, this is best used for leaf nodes that are widget elements and true extensions of HTMLElement, most other interops with javascript are probably left to ports. There's a type of web component called a custom built-in element and I recommend not using it because webkit won't support it. I used mustache for templating and I really like how's it's templating helpers supported my web components. Communication from Elm to a web component can be done via attributes and properties I suggest attributes as the better way to go because it opens up the possiblity of using the web components in server side apps that may be part of your enterprise. Web component to Elm communication is via event dispatching from the web component and "catching" it using [Html.Events on](https://package.elm-lang.org/packages/elm/html/latest/Html-Events#on) function for custom events.