View Source Headless UI Components for Phoenix

Unstyled, accessible UI components for Phoenix and Phoenix LiveView. To be styled with the CSS framework of your choice.

[!WARNING] This project is in a very early stage - see Components list below.

Preview

Demo

See demo website

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

Components

ComponentFunctionsStatus
Avataruse_avatar/1βœ… Done
Checkboxinput/1βœ… Done
Clipboarduse_clipboard/1βœ… Done
Comboboxuse_combobox/1πŸ—οΈ In progress
CommandπŸ—οΈ In progress
DialogπŸ—ΊοΈ Planned
File PreviewπŸ—ΊοΈ Planned
Input OTPπŸ—ΊοΈ Planned
Popoveruse_popover/1βœ… Done
Radio buttonπŸ—ΊοΈ Planned
TabsπŸ—ΊοΈ Planned
Text inputinput/1βœ… Done
TextareaπŸ—ΊοΈ Planned
Toggleuse_toggle/1βœ… Done

Installation

The package can be installed by adding headless to your list of dependencies in mix.exs:

def deps do
  [
    {:headless, "~> 0.1"}
  ]
end

Include JavaScript package in your app.js:

// assets/js/app.js

// import and start headless
import headless from "headless"
headless.start()

// ...

// configure LiveSocket
let liveSocket = new LiveSocket('/live', Socket, {
  // ...

  // configure dom hook
  dom: headless.dom
})

Usage

Headless components are meant to be used as building blocks for your own components. Most components are built using use_* functions that expose the necessary HTML attributes to provide the functionality leaving all tag rendering to the user. This way every element can be 100% customized.

See example app components.

defmodule MyAppWeb.Components do
  use Phoenix.Component
  import Headless

  attr :src, :any
  attr :alt, :any
  attr :initials, :string

  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
end

Adding your own Alpine components

If you want to add your own Alpine components you can import the bundled Alpine like this:

// assets/js/app.js

import headless, { Alpine } from "headless"

Alpine.data("my_custom_component", () => ...)

headless.start()

Development

# Start development server with examples
mix phx.server

# Update bundled Alpine
curl -L https://unpkg.com/@alpinejs/csp/dist/module.cjs.js > ./apps/headless/assets/vendor/alpine.js

Inspirations