teamon

teamon

After spending some time recently researching various ways of building modern UIs I came to one sad conclusion: if one wants to build a modern interactive application in Phoenix (+ LiveView) there are not many choices of complex components, like for example a dropdown with search. Incorporating existing solutions requires data manipulation that are not easy to keep track of especially in LiveView context.

That’s how headless was born - a collection of unstyled, UI components for Phoenix and Phoenix LiveView.

https://github.com/teamon/headless

Goals & Rules

  • Provide unstyled Phoenix components as building blocks for your own UI components

  • If something can be achieved with HTML and CSS only, it should be done with HTML and CSS only (no-JS)

  • Where JS is required, use Alpine.js

  • Use Alpine.data() instead of inline markup

  • Components must work with standard Phoenix controllers (dead views)

  • Components must work with Phoenix LiveView

  • Components must work with standard Phoenix forms

  • Components must be accessible (aria attributes, keyboard navigation, focus, etc.)

Non-goals

Providing 100+ HTML components - headless should focus only on tricky components that are non-trivial to implement correctly, something like Headless UI for React and Vue.

Future goals

In the perfect world there would be a collection of headless components and then some UI libraries built on top of that providing elegant styling.

Status

Very very early, Proof of Concept stage.

So far I did an example <.toggle> component that uses a checkbox under the hood and my nemesis <.combobox/> that tried to leverage all the things that we get with Phoenix & LiveView and provide a good client-side experience on top of that.

I would love to hear any feedback on the idea itself, the implementation, etc.

Showing Posts 1 to 5

sodapopcan

sodapopcan

It’s good to see more effort in this space! Are you aware of Doggo? It’s already quite far along though still a work in progress. A lot of the more complex JS has not been implemented yet. This is not my project so I can’t speak to plans there but I am using it in a personal project and currently converting a production app over to it.

For myself, the Alpine dependency makes Headless a non-starter. I’m not recommending you don’t use it, but I feel JS commands+hooks are a better route in terms of longevity within the ecosystem. Using Alpine isn’t a bad thing per se as lots of Phoenix-folks still use it, but you’re boxing out the dependency-conscious among us… which is also not necessarily a bad thing per se as it’s providing some variety. Good work, overall!

greven

greven

To add to what @sodapopcan said, great initiative, we need more in the community! But I agree with the Alpine sentiment. I really like Alpine but it has a big drawback for me is the CSP limitation: CSP — Alpine.js

I rather deal hooks that having problems and workaround the CSP. Even though as a library AlpineJS is pretty nice regarding DX. :slight_smile:

teamon

teamon OP

Thank you for the feedback.
I’ve seen doggo, but I think the goals are different (see Non-goals above)

I personally find JS commands a bit hard to read and understand, especially with all the interpolations but I do get the point about being dependency-less. I’m coming from the need to replace “full” SPA framework like Vue/React with Phoenix and just a little of JavaScript and Alpine seems to be a sensible solution.

As for the CSP so far I’ve used the mentioned Alpine.data() and x-bind heavily. I haven’t tried it but there’s a chance it even current version of combobox would work with CSP version of Alpine.

teamon

teamon OP

After researching more into ways of bulding a headless library I decided to go with “api-only” approach like Sprout UI did.

And there’s a demo website now: https://headless.fly.dev

teamon

teamon OP

Update

After trying multiple approaches to provide the headless API-only components I decided to go with standard Phoenix components and an optional optimization compiler.

The are two problems with API-only components when used like this:

  import Headless

  def avatar(assigns) do
    ~H"""
    <.use_avatar :let={a} src={@src}>
      <div {a.root}>
        <img {a.image} alt={@alt} />
        <div {a.fallback}><%= @initials %></div>
      </div>
    </.use_avatar>
    """
  end
  1. There is no compile time validation for a.root etc. calls. Using a wrong {a.wrong} key will result in runtime error
  2. Since LiveView don’t know what a.root and others will contain it can’t properly track changes and has to mark all these places as dynamic rerender and send them on every update.

This could be solved with component macros but we don’t have them (yet :wink: ).

Instead I wrote a proof of concept “preprocessor”. The only change in client code is to replace def with defc, like this:

  import Headless

  defc avatar(assigns) do
    ~H"""
    <.use_avatar :let={a} src={@src}>
      <div {a.root}>
        <img {a.image} alt={@alt} />
        <div {a.fallback}><%= @initials %></div>
      </div>
    </.use_avatar>
    """
  end

Headless compiler will discover all known use_* calls and inline attributes, resulting in a flattened HEEX template like this:

  import Headless

  def avatar(assigns) do
    ~H"""
    <div x-data="hs_avatar">
      <img x-ref="image" data-src={@src} alt={@alt} />
      <div x-ref="fallback"><%= @initials %></div>
    </div>
    """
  end

This gives us the best of both worlds - easy to use function components with a standard syntax and compile-time validation plus performance improvement.

Demo is now a standard phoenix app so you can see how headless could be used as a dependency.

— All posts loaded —

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
fuelen
Hi all! I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas. You...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
akoutmos
@hugobarauna, Dr. Dimitrios Koutmos (my brother) and I (Alex Koutmos) have been hard at work on writing a book on how you can use Elixir ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews