adw632
Adobe Spectrum 2 web components with LiveView
I was just looking at the new Adobe Spectrum 2 web components which were released a couple of days ago with an article also on Tech Crunch.
I have had an initial look and I doubt one will find a more tested and documented web component library that works across browsers and devices. Adobe has made a substantial investment in their design language and tokens with great care for keyboard navigation and accessibility of every component so it strikes me as a solid basis to build framework free web apps on. Given Adobe are literally betting the farm on it I expect it to be a solid app UI and CSS choice with long term viability:
Adobe will start rolling out Spectrum 2 by bringing it to its web and iOS apps first, starting with a first set of updates to the web apps in early 2024. Over time, it will also come to the company’s flagship desktop tools like Photoshop, Lightroom and Premiere Pro.
In total, Adobe plans to bring its new design language to well over 100 apps. Rolling out a new design system across such a wide number of applications is never an easy task. Just look at how long it took Google to bring its Material Design system to the majority of its services. Still, Adobe expects to be able to roll out Spectrum 2 to quite a few applications in 2024.
Abobe provide both web component explorer and a CSS explorer as you can also style html without the web components, storybooks also available.
I am looking at experimenting with Adobes web components in conjunction with Chris Nelsons (@superchris) live_elements. Live elements improves the ergonomics of working with web components which he covers in his ElixirConf talk.
I think its exciting that with web components we can build rich framework free UI’s using component collections like Adobe Spectrum 2 and Shoelace.
Web Components just may be the right strategy to filling a large gap in the Phoenix ecosystem which currently lacks a capable component library.
Most Liked
TunkShif
The idea of headless components based on web components is great, but it is not easy to implement such a component system, at least from what I have tried.
First I want to make it clear that Web Component is a set of different technologies which you can use altogether or separately. It consists of three different web apis: custom element, shadow DOM, template and slot
- Custom element allows you to define your own HTML tags and associated lifecycle callbacks for the HTML element
- Shadow DOM allows you to create an isolated DOM environment separated from the normal DOM (also called the ‘light DOM’). A shadow DOM tree is usually created and attached to a custom element when the element is mounted. Styles in light DOM won’t leak into the shadow DOM environment and also you cannot use normal styling method (like using CSS selectors targeting a specific class name) won’t apply to the shadow DOM tree content.
- Slot API is only available inside a shadow DOM context. It allows you to ‘slot’ light DOM content into the shadow DOM tree.
Since they are three different APIs, you can use them combined or just use one of them in your web component .
And here I want to list some major problems I had when I tried to implement a headless and accessible UI component library using web components for Phoenix LiveView.
Styling issues with Shadow DOM
What @Sebb asked is actually a good question about how styling web components works.
Let’s first think about how to implement a custom button with web component. In most of these JS UI component libs, take Bits UI as an example, the <Button.Root>Click Me</Button.Root> component renders just a single button element, the rendered html would look like <button>Click Me</button>.
But below is how it usually implemented in most web component UI libs (like Sholace or Carbon), the actual rendered html would look like:
<my-button>
#shadow-root
<button>Click Me</button>
</my-button>
here is how it looks like inside Chrome DevTools

The difference is that your actual button element now is being wrapped inside a custom element’s shadow root. You cannot use normal CSS selectors to target the button element, so the following CSS would not work:
my-button button {
color: red;
}
Also, if you’re using tailwindcss to set a bunch of class names on the custom element like <my-button class="text-blue"></my-button>, you’re just setting styles for the wrapper element, not on the actual button element inside the shadow root. It won’t work either if you add tailwind class names directly to the button element, because all your tailwind classes live on the light DOM style, and they won’t affect the shadow DOM styles.
Here’s an example and you can check the demo online here:
![]()
The paragraph element and the button element are both inside the shadow DOM, but only the paragraph text color is affected by the text-blue class. That’s because the paragraph inherits color styles from the parent element, but the button element has default text color set by the browser user agent.
So if you want to style an element inside a shadow DOM, you’ll have to use part attribute and ::part() CSS selector, so that you can target an element inside shadow DOM. This is what Showlace used to allow some component customization, check the Shoelace button customization example.
Other alternatives include using <slot> to slot light DOM element inside shadow DOM, so you can styling the slotted element. Or you can just ditch shadow DOM and just use custom element API, but you won’t be able to use the slot API.
You may ask, what if we just remove the extra <button> element wrapper inside the shadow DOM, and use our custom element as the button container, just like:
<my-button>
#shadow-root
<p>Click Me</p>
</my-button>
Well, then you have to take care of accessibility issues with your own custom button element, since <button> element has default screen reader support and focus management provided by the browser. You will need to add a bunch of ARIA-related attributes to your custom element to make it fully accessible.
Not easy to design a composable and flexible component API
One thing that I like about headless UI is that they’re very flexible and can be easily composed, but it’s hard to acheive a similar component API design with just web compoennts.
Let’s take the AlertDialog component form Bits UI as an example,
<AlertDialog.Root>
<AlertDialog.Trigger />
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialog.Content>
<AlertDialog.Title />
<AlertDialog.Description />
<AlertDialog.Cancel />
<AlertDialog.Action />
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
How would you map it into a web component? You may think, we can just wrap every component part into a web component, then it would be something like:
<alert-dialog-root>
<alert-dialog-trigger></alert-dialog-trigger>
<alert-dialog-overlay></alert-dialog-overlay>
<alert-dialog-content>
<alert-dialog-title></alert-dialog-title>
<alert-dialog-description></alert-dialog-description>
</alert-dialog-content>
</alert-dialog-root>
But it won’t work that easily, most of those headless UI libs heavily relies on Context API from the UI framework to be able to share states for all those component parts.
According to the WAI-ARIA Guide, the element served as the alert dialog container should have a aria-labelledby attribute associated with the dialog title element id and a aria-describedby attribute associated with the dialog description element id for accessibility,
We don’t set any of these attributes manually since the UI lib did it for us, and this is achieved by the Svelte Context API, so that the parent and child components can share states. But it’s not available cross different web components, as documented in the Custom elements - Caveats and limitations of Svelte API.
So if you want to achieve a similar component API design for web component, it may require some manual JS-side work.
Client-Side DOM changes and Server-Side DOM changes
This what I have talked about in this post: Help me choose between LiveView and Vue - #54 by TunkShif
Let’s take the Switch component form Bits UI as an example now, for accessibility, when the state of a switch component changes, the associated aria-checked attribute in the switch element should also be changed respectively.
If this DOM change is controlled by JS and happens on the client side, then a following LiveView DOM patch will just overwrite the DOM change, making it return back to the original HTML template sent from the server.
Other examples like tooltip, hover card and popup, these components also involves client side DOM changes, you hover on something, and the associated popup element is set to show, and this is controlled by the JS side. You’ll have to use something like LiveView hooks to sync the client side DOM changes with server side chanegs.
So generally speaking, I don’t think web component is a good choice to build lower primitives for headless component that requires flexibility, since it is initially proposed to create element with functionality and styling completely encapsulated.
From my point of view, web component is good for these two cases:
- Build framework-agnostic design systems. Shoelace is the best example, and it provides the most customizable component designs.
- Encapsulate a single functionality with web component. Like the lit-google-map, emoji-picker-element, giscus-component.
And finally, in my opinion, the biggest obstacle to build an accessible component library for LiveView is that, to build components with rich interactions and full accessibility like keyboard navigation, focus management, typeahead support and popup anchor position, and also taking browser differences into consideration, JavaScript is unavoidable. But once we’re managing DOM states from the client side, it finally falls into fighting with synchronizing client-side states with LiveView DOM patch.
This is a really long thread, and I just want to share some of my own experience with Web Components and LiveView. Thanks for your patience if you’re watching to the end. ![]()
adw632
@TunkShif your post is very relevant and I have been on this journey too. Svelte 5 is in preview and I expect that it will solve the shared state aspects of web components.
In terms of styling, I have not had any issues styling carbon, none. It uses sass based styles so I have been able to customise all of the theme tokens and override styles on component parts very easily and implement dynamic theme switching for light and dark mode.
I am not a fan of using Tailwind classes on elements as it leads to hard coding style in the code. It may be fine for one off web sites but I want a theme based approach for apps.
Tailwind also rubs against web standards and shadow DOM parts as there is no shadow DOM parts in your markup to hang utility tags on. Sure you can use @apply in CSS but then it’s easier to just write the CSS you want.
Tailwind is the current trend and will like all things fade into “bad idea, what drugs were we all on?” Perhaps in a year or two when the next Kool Kid like UnoCSS catches on.
I will say Shoelace is a non starter from accessibility point of view because it has always been in a state of “I don’t know, will try harder” by the maintainer when it comes to accessibility. Then the maintainer objects to including navigation components like a navbar! The UI shell is a critical concern of accessibility, so in the final analysis Shoelace is a non solution to basic application requirements. Adobe Spectrum V1 turns out to have a similar dilemma with no app shell navigation components. The hope here is for Spectrum 2 to deliver the goods.
That basically leaves Carbon as about the only viable accessibility focused web component library. Take a moment to read the depth of the concern just for the UI shell navigation. Check the Usage, Style and Accessibility sections on that page.
Some may think accessibility is nice to have. The reality is that it is table stakes for procurement in government systems and countries that have disability discrimination regulations. So without competent a11y, no one can purchase your software or solution, and yes they do have teams dedicated to evaluating conformance.
LiveSvelte does deal with this, but it’s not much of a “fight”, still it is a concern that needs due consideration.
However Chris McCord confirmed the segregation with Shadow DOM not needing phx-update="ignore" in this thread:
A component that doesn’t use the shadow DOM will get fouled up on liveview updates.
Of course there could be cases where the entire element is replaced due some parent/ancestor mutation, then sure you will lose the UI state, so its probably a good idea to ensure elements have unique IDs to minimise replacement. Live Elements does this here.
Given that custom elements and web components are W3C standards I think it is important to identify precisely where LiveView trips up and address these issues head on. I have no doubt I will hit some edge cases that may require some workarounds.
This is why I am conducting a series of experiments to understand the limitations and what the solutions are and the general feasibility. My view is that Phoenix must work ultimately well with web components to remain viable long term, so any hiccups or shortcomings will necessarily have to be addressed.
I do know from my own experiments that LiveSvelte has full reactivity with a very simple hook so that both client and server reactivity worked harmoniously together. There were some other issues I discovered with that solution, which is why I am not using it currently.
Once I have completed my experiments I will post some starter projects and guides for others to follow and clearly document any caveats, workarounds or show stoppers that I have identified.
chrismccord
I hadn’t been aware of spectrum before this. Looks great. Is the current adobe spectrum component docs/explorer spectrum 1 or 2? It’s not clear from the adobe pages whether version 2 release is TDB or already publicly available. First class web components UI libraries with focus on a11y is great and should be able to replace usage of Hooks in many cases.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex











