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"}]
endRequires 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:
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";Import the stylesheet, which carries the
[x-cloak]rule and the marquee keyframes.@import "../../deps/pine_ui_phoenix/priv/static/pine_ui.css";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
endA 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.
| Layout | card · table · blockquote |
| Forms | button · text_input · textarea · checkbox · radio_group · select · select_menu · switch · range_slider · rating · date_picker · copy_to_clipboard |
| Feedback | alert · badge · banner · progress · progress_circle · progress_steps · toast_group · toast_trigger |
| Navigation | accordion · tabs · breadcrumbs · pagination · navigation_menu · menubar |
| Overlays | modal · full_screen_modal · slide_over · dropdown_menu · context_menu · popover · hover_card · tooltip · command |
| Media | image_gallery · carousel · video · monaco_editor |
| Animation | marquee · 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:
- The calendar grid is computed in Elixir. Pines builds it in Alpine; here
Datehandles leap years and month lengths, and the days are real<button>s in the DOM. text_animationneeds no GSAP. The original loads GSAP from a CDN; this splits the text server-side and animates with CSS transitions.- Alpine state is JSON-encoded, never interpolated. Building
x-databy string interpolation breaks on apostrophes and allows expression injection. - Classes merge rather than concatenate, so
<.button class="bg-red-500">actually overrides the default background. See theming. - Accessibility is wired properly — real ARIA bindings, unique ids, keyboard navigation,
and
prefers-reduced-motion.
Documentation
- Installation — Tailwind, Alpine, plugins, hooks
- LiveView integration — DOM patching, server-driven state, forms, streams
- Theming — class merging, dark mode, accent colours
- Upgrading to v0.2 — from v0.1.x
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 shipped priv/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.