Adobe Spectrum 2 web components with LiveView

I understand that this will be killed by LV.
But what about state held in a variable inside the component?

So it should be possible to create LV-aware-web-components that store their internal state just like that (“sticky”)…?

Are 100% sure? If I remember correctly if a parent creates a context all childs can use it, but its a long time ago I looked into solid.

What does the component here refer to? If you mean a web component, as long as the old element is not being replaced or deleted, the states associated to that element will be preserved.

And for the previous counter example, the actual DOM change should be just deleting the data-count attribute according to how morphdom works. So if it’s a custom element, the states stored inside it should just be fine.

Yeah, but interacting with the private attributes directly is a bit too hacky. So what I usually do is just execute JS Command from JavaScript.

Did some quick research to find out that Solid somewhat is different. :thinking: Solid Context actually seems to work across different web components and it is indeed can be achieved due to how Solid Signal works. Here’s an example I found: Solid Playground . Good to know that!

I’m also hoping that the web standard could evolve for the better!

Adopted Stylesheet is interesting to explore, there’s already attempts to integrate it with tailwindcss-like libs: Add Tailwind styles to shadow DOM

right, and that’s why I think this could also be possible in svelte 5, as that will be a completely new framework under the hood based on signals.

@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.

9 Likes

How are you doing with web components? Did you decided to use Carbon or did you find something better? Did you look into HTMX with Phoenix?

2 Likes

So I finally came to the conclusion, that it is not feasible to use (most) webcomponents in LV right now. Its too hacky. If I find the time I’ll write my findings down.

But it is possible to write LV-aware WCs, so I had a look at Solid again and can confirm that sharing context across WCs also works directly in the browser, so no JSX needed.

context-provider.jsx

import { customElement } from 'solid-element'
import { createContext  } from 'solid-js'

export const TheContext = createContext('default')

customElement('x-a', { foo: 'default foo prop' }, (props, { element }) => {
    console.log(element, props)
    return (
        <fieldset>
            <legend>A</legend>
            <TheContext.Provider value={props.foo}>
                {props.children}
                <slot></slot>
            </TheContext.Provider>
        </fieldset>
    )
})

context-user.jsx

import { customElement } from 'solid-element'
import { useContext } from 'solid-js'
import { TheContext } from './my-context-provider'

customElement('x-b', () => {
    const ctx = useContext(TheContext)
    console.log("ctx:", ctx)
    return (
        <fieldset>
            <legend>B</legend>
            {ctx}
        </fieldset>
    )
})
<!DOCTYPE html>
<head>
    <script type="module" src="./dist/myContextUser.es.js"></script>
    <script type="module" src="./dist/myContextProvider.es.js"></script>
</head>
<body>
     <my-context-provider>
        <my-context-user></my-context-user>
    </my-context-provider>
    <my-context-provider foo="Foo">
        <my-context-user></my-context-user>
    </my-context-provider>
</body>
</html>

image

3 Likes