Pine UI 🌲

Hex.pmDocsLicense

The Pines UI library — all 42 elements — ported to Phoenix LiveView function components.

Pines is DevDojo's Alpine.js + Tailwind CSS component library, distributed as HTML you copy and paste. This package turns it into real Phoenix components with declarative attr/slot APIs, compile-time validation, dark mode, and the LiveView plumbing Alpine needs to survive DOM patching.

<.card title="Getting started">
<p>Every Pines element, as a Phoenix component.</p>
<:footer>
<.button phx-click={JS.dispatch("pine:open", to: "#docs")}>Read the docs</.button>
</:footer>
</.card>
<.slide_over id="docs" title="Documentation"></.slide_over>

Installation

def deps do
[{:pine_ui_phoenix, "~> 0.2.0"}]
end

Requires Elixir 1.15+ and phoenix_live_view >= 0.20.4 — which covers 0.20.x, 1.0, 1.1 and 1.2.

Three further steps are required. Skipping them is the difference between working components and components that render unstyled or flash their contents on load:

  1. Point Tailwind at the package, or every class it uses is purged from your production build.

    // Tailwind v3 — tailwind.config.js
    content: [..., "../deps/pine_ui_phoenix/lib/**/*.ex"]
    /* Tailwind v4 — app.css */
    @source "../../deps/pine_ui_phoenix/lib";
  2. Import the stylesheet, which carries the [x-cloak] rule and the marquee keyframes.

    @import "../../deps/pine_ui_phoenix/priv/static/pine_ui.css";
  3. Install Alpine and wire up pineDom(), so Alpine state survives LiveView patches.

    import { pineDom, PineHooks } from "../../deps/pine_ui_phoenix/priv/static/pine_ui.js"
    const liveSocket = new LiveSocket("/live", Socket, {
    params: { _csrf_token: csrfToken },
    hooks: { ...PineHooks },
    dom: pineDom()
    })

Full details, including which Alpine plugins each component needs, are in the installation guide.

Usage

defp html_helpers do
quote do
use Phoenix.Component
use PineUiPhoenix
# ...
end
end

A stock Phoenix app already generates its own <.button>, <.table> and <.modal> in CoreComponents, so resolve the clash however suits you:

use PineUiPhoenix, except: [:button, :table, :modal]
use PineUiPhoenix, only: [:command, :marquee, :rating]
use PineUiPhoenix, prefix: :pine # <.pine_button>

Components

All 42 Pines elements, across both the "Tailwind and Alpine" and "Tailwind Only" groups.

Layoutcard · table · blockquote
Formsbutton · text_input · textarea · checkbox · radio_group · select · select_menu · switch · range_slider · rating · date_picker · copy_to_clipboard
Feedbackalert · badge · banner · progress · progress_circle · progress_steps · toast_group · toast_trigger
Navigationaccordion · tabs · breadcrumbs · pagination · navigation_menu · menubar
Overlaysmodal · full_screen_modal · slide_over · dropdown_menu · context_menu · popover · hover_card · tooltip · command
Mediaimage_gallery · carousel · video · monaco_editor
Animationmarquee · retro_grid · typing_effect · text_animation

Fourteen of them need no JavaScript at all.

What this port changes

It is a port, not a transcription. Where the copy-paste original made sense as HTML but not as a component, it was rebuilt:

Documentation

Component reference: hexdocs.pm/pine_ui_phoenix.

Upgrading from v0.1.x

Every v0.1.x function name still works and is re-exported as a deprecated delegate, so your templates keep rendering. Three things do change — the LiveView floor, the palette, and the removal of :poison. See the upgrade guide.

Development

mix deps.get
mix test # component + cross-component contract tests
mix lint # format check, warnings-as-errors, credo
mix dev # demo app at http://localhost:4444
mix assets.watch # rebuild the demo's Tailwind CSS

mix dev runs a demo of every component with its source shown alongside each example. It serves the shippedpriv/static assets, so it exercises exactly what consumers get.

Credits

Design and behaviour come from Pines by DevDojo. This is an independent Phoenix port.

License

MIT — see LICENSE.