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.

10 Likes

Definitely let me know how it goes! I am curious to hear feedback from people using LiveElements and LiveState. Just one thing to point out, there is also (confusingly) a Rails project called live_elements. I think that Fly.io article is talking about the Rails thing not my project.

1 Like

Yes it mentions live view and then discusses a rails add on so I removed that reference.

I will be moving forward with the current Adobe Spectrum components as Spectrum 2 won’t land until next year, but at least I’ll be ready for it.

I plan to publish a step by step starter project so others can also explore this path.

5 Likes

I wanted to try shoelace and LV for a long time now, but the first steps seem hard.
Spectrum it did not know but it looks amazing.
If you have anything to share to get others going, it would be greatly appreciated!

I have a starter project and step by step instructions on setting it up which I will publish and link to on ElxirForums once the festivities settle down.

I also have some minor PRs to submit that were required to make everything work nicely with LiveElements.

5 Likes

Looking forward to it.
The spectrum web components are great. If you can show a good way to make it work nicely with LV (eg replace put-flash with their toaster, control modals and popovers, …) it’ll be amazing.

Question: I did not find anything but marketing announcements for Spectrum 2 - is there anything else?

It will launch in the new year, but setting up for Spectrum V1 will be more or less ready for v2.

I have experimented with Shoelace and Spectrum and I am now looking at the IBM carbon design system which includes app shell / navigation components that the others leave out and a huge focus on accessibility. Their documentation and design guidance is very comprehensive too.

1 Like

I tried using data-tables from carbon web components and tables with selectable rows in spectrum web components. When LiveView mounts, both tables work fine (rows select, columns sort). After the LV hydrates when the socket connects, the tables lose sorting and the checkbox get removed.

phx-update="ignore" fixes it, but then I cannot add more rows. How do you plan to update the table rows using web components and LV?

Does the data-table example (from GitHub - launchscout/live_elements_testbed: A testbed project (phoenix) to house integration tests for live_elements) work when you sort columns?

1 Like

The primitive way to do this is with hooks, because as far as I understand from your post JS manages the sort state, but there are nicer abstractions as far as was mentioned either in this post in upper comments or a similar one.

What would the hooks do?

You can read about this in more details here: JavaScript interoperability — Phoenix LiveView v0.20.2. Basically you can set phx-update to ignore and pass the state directly to JS component, then handle everything else with JS.

I haven’t got to the data tables yet, however for events emitted by components that’s where live-elements comes in, and it also creates function component wrappers so it works with liveview and wires up any specified events.

On the return path for data from the server ultimately we are rendering rows into phoenix component slots which are also mapped to custom element slots by live elements. I am expecting this will just work also with the newer streams approach.

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.

7 Likes

It’s v1.

An easy way to use webcomponents would be a great step forward for LV.
But: it’s not easily done. The work of Chris helps, but its still hard.

The lack of a decent component library is what is keeping us in JS-framework-land for now.

2 Likes

It’s currently V1. The v2 should be out soon.

Yes I agree. I think web components are the answer to competing with front end frameworks, to the point many front end frameworks are using these capable frameworks like Spectrum and Carbon which bake in a11y.

I got both the Shoelace and Spectrum stuff working but both lacked a few navigation elements and reactive things like sidenav/drawers, which I think is odd because that’s a big part of accessibility and keyboard navigation.

I then moved onto integating the Carbon Design System V11 web components into my asset pipeline which does deal with these concerns.

Another option I considered was compiling the Svelte bits-ui as a very clean set of unstyled, headless accessibility focused custom elements which has extremely minimal dependencies as it’s built on the melt-ui component builder by the same authors (which was inspired from working with Radix UI).

Bits UI is used and themed in shadcn-svelte using tailwind and is basically just a copy paste component collection built using bits-UI with some default base layer theming and a handful of color tokens.

I still might do this as an experiment to see what the snags are.

So I believe a really nice headless UI system based on custom elements is within reach. It’s more or less just a re-packaging of bits UI as custom elements using the svelte compilation flag to emit custom elements.

That could serve as the minimal headless UI foundation for Phoenix, and also align nicely with the current use of tailwind also (the bits UI component examples show the element structure and the code examples show use of tailwind classes for example styling).

I would suggest adopting a base theming layer and some design tokens very much as shadcn-svelte has done as a theme engine for Pheonix.

Then add in the custom element event helper for wiring events back to liveview and it’s getting tantilisingly close.

The next step beyond this would be a much larger step, to make custom elements a first class engine of how “LiveView V2” works as a possible future.

This would require Phoenix to render the elements into a declarative shadow dom template on first render.(sounds complex but it’s literally just adding a template tag) so that we get server side rendered custom elements without any JS/node. We could literally beat the JS frameworks on this.

The other half is to generate a custom element and transpile the render function to JS, which gets shipped to the client as a custom element. Hologram already has a nice Elixir to JS transpiler. The html should include any client side scripts and styles. A good example would be callbacks for client side reactivity, keyboard and navigation events etc.

Liveview interaction with the client would then become more like LiveState and just exchange delta state and events over a Phoenix channel, so rather than pushing html / Dom patching, it’s pushing state patches, and the custom elements emit events that are forwarded to the server and handled just like they currently are (basically what is done in LiveState).

Why do all that?

  • Well you can embed Phoenix components in other applications just by adding some published custom elements from your app to a page anywhere.
  • It can also allow a pathway to LiveView SPA’s using a wrapper with a simple reactive state cache.
3 Likes

Can you expand where LiveView falls short here? webcomponents/custom elements by their nature are “just tags” so the LiveView integration story vs where you’d use a hook today works just fine. For example, here’s the LiveBeats Flash hook vs Flash web component:

// <div phx-hook="Flash"> ...
Hooks.Flash = {
  mounted(){
    let hide = () => liveSocket.execJS(this.el, this.el.getAttribute("phx-click"))
    this.timer = setTimeout(() => hide(), 8000)
    this.el.addEventListener("phx:hide-start", () => clearTimeout(this.timer))
    this.el.addEventListener("mouseover", () => {
      clearTimeout(this.timer)
      this.timer = setTimeout(() => hide(), 8000)
    })
  },
  destroyed(){ clearTimeout(this.timer) }
}

// vs
// <lb-flash> ...
class Flash extends HTMLElement {
  connectedCallback(){
    let hide = () => liveSocket.execJS(this, this.getAttribute("phx-click"))
    this.timer = setTimeout(() => hide(), 8000)
    this.addEventListener("phx:hide-start", () => clearTimeout(this.timer))
    this.addEventListener("mouseover", () => {
      clearTimeout(this.timer)
      this.timer = setTimeout(() => hide(), 8000)
    })
  }
  disconnectedCallback(){ clearTimeout(this.timer) }
}

customElements.define('lb-flash', Flash);

WC can get more complex when shadowdom comes into play, but then it’s also a nice segregation from LV DOM patching so you can get around the phx-update=ignore steps. Having this available in a LV collocated/same file way would be great, but what roadblocks exist for you where WC + LV is not “easy to use” ?

3 Likes

The main things I found “not easy to use” with LV/WC integration were:

  • sending complex data in required json serialization
  • handling custom events in LV required a hook

All of these are certainly possible to do yourself, it just gets a bit tedious IMHO, which is my I created live_elements. It uses macros to make custom elements work just like LV functional components.

2 Likes

Agree. Ive been using it :laughing:

I will raise a couple of PRs for live elements when I’m back to the office which handle some edge cases.

I’m also planning to automate the CEM generation with a watcher as part of my build pipeline, which might result in a documention PR.

That’s only true for “dumb” components that get all information they need from the tag-structure. But when they have some methods or emit events it’s another story. And it’s beyond me.

@andrewh will hopefully soon share his findings, so we get a picture what really needs to be done.

This looks promising. Complete, headless, tailwind-stylable web-components with decent a11y and a painless LV-wrapper would be the perfect solution.