Pine UI 🌲 — the Pines component library for Phoenix LiveView.
Pines is an Alpine.js + Tailwind CSS component library. This package ports it to Phoenix
function components, with declarative attr/slot APIs, dark mode, and the LiveView
plumbing that Alpine needs in order to survive DOM patching.
Installation
def deps do
[{:pine_ui_phoenix, "~> 0.2.0"}]
endThree further steps are required — the components will not work without them. They are covered in full in the Installation guide:
Tell Tailwind to scan this package, or every class it uses will be purged from your production build and the components will render unstyled.
// 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 that stops overlays flashing their contents on page load.@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() })
Usage
Import everything in your html_helpers/0:
defp html_helpers do
quote do
use PineUiPhoenix
# ...
end
endThen use any component:
<.card title="Getting started">
<p>Pine UI brings Pines to Phoenix.</p>
<:footer>
<.button>Read the docs</.button>
</:footer>
</.card>Avoiding name collisions
A stock Phoenix 1.7/1.8 app generates a CoreComponents module with its own <.button>,
<.table> and <.modal>. Since both are imported into the same scope, you must resolve
the clash one of three ways:
use PineUiPhoenix, except: [:button, :table, :modal]
use PineUiPhoenix, only: [:command, :marquee, :rating]
use PineUiPhoenix, prefix: :pine # <.pine_button>, <.pine_table>prefix: generates delegating wrappers, which means the prefixed calls lose compile-time
attribute validation. only:/except: keep it, so prefer them where you can.
Upgrading from v0.1.x
Every v0.1.x function name still works and is re-exported here as a deprecated delegate, so upgrading does not break your templates. See the Upgrading to v0.2 guide for the old-to-new mapping.
Summary
Deprecated (v0.1.x)
Functions
The component registry: {key, module, [{function, arity}]} for every component.
Imports Pine UI components into the calling module.
Third-party JavaScript each component needs, keyed by registry key.
Deprecated (v0.1.x)
Functions
@spec __components__() :: [{atom(), module(), keyword(non_neg_integer())}]
The component registry: {key, module, [{function, arity}]} for every component.
Used by the demo app and by the cross-component contract tests, so neither can drift out of sync with what the library actually ships.
Imports Pine UI components into the calling module.
Options
:only— import just these components, by registry key.:except— import everything but these.:prefix— an atom prepended to every function name, e.g.prefix: :pinegives<.pine_button>. Note that prefixed components are delegates, so they do not get compile-timeattrvalidation.
:only and :except are mutually exclusive.
Alpine attributes
This macro also defines __global__?/1 on the calling module, registering x- as a global
attribute prefix. Without it, every Alpine directive written at a call site —
<.dropdown x-on:keydown.escape="open = false">— produces warning: undefined attribute "x-on:keydown.escape". The attribute is still
forwarded to the component's @rest, so the page works; the cost is a noisy build, which
--warnings-as-errors turns into a broken one.
Phoenix resolves global prefixes against the module containing the call site, which is why the library cannot register this centrally on its own modules.
If you already pass your own prefixes —
use Phoenix.Component, global_prefixes: ~w(x- data-my-)— that definition wins and this macro leaves it alone. In that case include x- yourself.
For the same reason, use PineUiPhoenix must come after use Phoenix.Component.
Third-party JavaScript each component needs, keyed by registry key.
Components absent from this map need nothing beyond Alpine core. Used to generate the requirements table in the installation guide, so the docs cannot drift from the code.
iex> PineUiPhoenix.requirements()[:modal]
[:alpine_focus]