PUI Usage Guide
Copy MarkdownThis guide will help you integrate PUI into your Phoenix LiveView application.
Installation
1. Add PUI to your dependencies
Add pui to your mix.exs:
defp deps do
[
{:pui, "~> 1.0.0-alpha"}
]
endThen run:
mix deps.get
2. Configure CSS
Import PUI's CSS into your application's CSS file (e.g., assets/css/app.css):
@import "tailwindcss" source(none);
@source "../css";
@source "../js";
@source "../../lib/your_app_web";
/* Add PUI source path */
@source "../../deps/pui";
@import "../../deps/pui/assets/css/pui.css";
/* Your other imports... */3. Configure JavaScript Hooks
Import PUI's JavaScript hooks in your assets/js/app.js:
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import { Hooks as PUIHooks } from "pui";
const csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
const liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: {
...PUIHooks,
// Your other hooks...
},
});
liveSocket.connect();4. Use PUI in Your LiveViews
Add use PUI to your LiveView modules to import all components:
defmodule MyAppWeb.DemoLive do
use MyAppWeb, :live_view
use PUI
def render(assigns) do
~H"""
<div class="space-y-4">
<.button>Click me</.button>
<.input type="text" placeholder="Enter text" />
</div>
"""
end
endComponents Overview
PUI provides the following components:
Button
<.button>Default Button</.button>
<.button variant="secondary">Secondary</.button>
<.button variant="destructive">Delete</.button>
<.button variant="outline">Outline</.button>
<.button variant="ghost">Ghost</.button>
<.button variant="link">Link</.button>
<!-- Sizes -->
<.button size="sm">Small</.button>
<.button size="default">Default</.button>
<.button size="lg">Large</.button>
<.button size="icon">
<.icon name="hero-heart" class="w-4 h-4" />
</.button>
<!-- With navigation -->
<.button navigate={~p"/path"}>Navigate</.button>
<.button patch={~p"/path"}>Patch</.button>
<.button href={~p"/path"}>Link</.button>Input
<!-- Basic inputs -->
<.input type="text" placeholder="Enter text" label="Name" />
<.input type="password" placeholder="Enter password" label="Password" />
<.input type="email" placeholder="you@example.com" label="Email" />
<.input type="file" label="Upload file" />
<!-- With Phoenix forms -->
<.form for={@form} phx-change="validate">
<.input field={@form[:name]} placeholder="Enter your name" />
<.input field={@form[:email]} type="email" />
</.form>
<!-- Textarea -->
<.textarea field={@form[:description]} placeholder="Enter description..." />
<!-- Checkbox -->
<.checkbox id="terms" label="Agree to terms" />
<.checkbox id="newsletter" label="Subscribe to newsletter" checked />
<!-- Radio buttons -->
<.label class="flex items-center gap-2">
<.radio name="plan" value="basic" /> Basic
</.label>
<.label class="flex items-center gap-2">
<.radio name="plan" value="pro" /> Pro
</.label>
<!-- Switch/Toggle -->
<.switch id="notifications" label="Enable notifications" />Dropdown Menu
<PUI.Dropdown.menu_button content_class="w-52">
<.icon name="hero-user" class="size-4" /> Update Profile
<:item navigate="/profile" shortcut="⇧⌘P">
<.icon name="hero-user" class="size-4" /> Profile
</:item>
<:item shortcut="⌘S">
<.icon name="hero-cog" class="size-4" /> Settings
</:item>
<:item variant="destructive">
<.icon name="hero-trash" class="size-4" /> Delete
</:item>
</PUI.Dropdown.menu_button>
<!-- With custom items -->
<PUI.Dropdown.menu_button content_class="w-52">
Options
<:items>
<.link navigate="/profile" role="menuitem">Profile</.link>
<PUI.Dropdown.menu_item>Settings</PUI.Dropdown.menu_item>
<PUI.Dropdown.menu_separator />
<PUI.Dropdown.menu_item variant="destructive">
<.icon name="hero-trash" class="size-4" /> Delete
</PUI.Dropdown.menu_item>
</:items>
</PUI.Dropdown.menu_button>Alert
<PUI.Alert.alert>
<:icon>
<.icon name="hero-check-circle" class="size-5" />
</:icon>
<:title>Success!</:title>
<:description>Your changes have been saved.</:description>
</PUI.Alert.alert>
<!-- Destructive variant -->
<PUI.Alert.alert variant="destructive">
<:icon>
<.icon name="hero-exclamation-triangle" class="size-5" />
</:icon>
<:title>Error</:title>
<:description>Something went wrong.</:description>
</PUI.Alert.alert>Popover
<PUI.Popover.base
id="demo-popover"
phx-hook="PUI.Popover"
data-placement="top"
>
<.button aria-haspopup="menu">Click Me</.button>
<:popup class="min-w-[250px] bg-foreground text-primary-foreground rounded-md shadow-md p-4">
<div class="space-y-2">
<p class="font-medium">Popover Content</p>
<p class="text-sm opacity-90">This is a popover with custom content.</p>
</div>
</:popup>
</PUI.Popover.base>Toast/Flash Notifications
Add the flash group component to your layout:
<PUI.Flash.flash_group
flash={@flash}
position="top-right"
live={true}
/>Send flash messages from your LiveView:
# Basic flash message
PUI.Flash.send_flash("Operation completed successfully!")
# Custom flash with HTML content
PUI.Flash.send_flash(~H"""
<div class="flex items-center gap-2">
<.icon name="hero-check-circle" class="size-5" />
<span>Success!</span>
</div>
""")
# Advanced flash with custom options
PUI.Flash.send_flash(%PUI.Flash.Message{
id: "my-flash",
type: :info,
message: "Custom message",
duration: 8, # seconds, -1 for no auto-dismiss
show_close: true,
class: "border-green-500 bg-green-100 text-green-800"
})Progress & Badge
<!-- Progress bar -->
<PUI.Components.progress value={75} />
<PUI.Components.progress value={45} class="h-4" />
<!-- Badge -->
<PUI.Components.badge>Default</PUI.Components.badge>
<PUI.Components.badge variant="secondary">Secondary</PUI.Components.badge>
<PUI.Components.badge variant="destructive">Error</PUI.Components.badge>
<PUI.Components.badge variant="outline">Outline</PUI.Components.badge>Dialog
<PUI.Dialog.dialog id="confirm-dialog">
<:trigger>
<.button>Open Dialog</.button>
</:trigger>
<:content>
<PUI.Dialog.header>
<PUI.Dialog.title>Are you sure?</PUI.Dialog.title>
<PUI.Dialog.description>
This action cannot be undone.
</PUI.Dialog.description>
</PUI.Dialog.header>
<div class="flex justify-end gap-2 mt-4">
<.button variant="outline" phx-click={PUI.Dialog.dismiss("confirm-dialog")}>
Cancel
</.button>
<.button variant="destructive" phx-click="confirm">
Delete
</.button>
</div>
</:content>
</PUI.Dialog.dialog>Select
<PUI.Select.select
id="country-select"
options={[
%{value: "us", label: "United States"},
%{value: "uk", label: "United Kingdom"},
%{value: "jp", label: "Japan"}
]}
placeholder="Select a country"
/>Global Component Imports
To make PUI components available in all your LiveViews without adding use PUI to each one, add the imports to your my_app_web.ex file:
defmodule MyAppWeb do
# ...
defp html_helpers do
quote do
# ... other imports
# Import PUI components globally
import PUI
import PUI.Input
import PUI.Button
import PUI.Dropdown
import PUI.Alert
import PUI.Popover
import PUI.Select
import PUI.Dialog
import PUI.Components
end
end
endCustomization
PUI components use Tailwind CSS classes and can be customized via the class attribute:
<.button class="bg-purple-600 hover:bg-purple-700">
Custom Purple Button
</.button>
<.input class="border-blue-500 focus:ring-blue-500" />Dependencies
PUI requires:
- Phoenix LiveView ~> 1.1
- Tailwind CSS v4
- esbuild for JavaScript bundling
Demo Application
Check out the demo/ directory in the PUI repository for a complete example application showcasing all components.
To run the demo:
cd demo
mix setup
mix phx.server
Then visit http://localhost:4000 to see all components in action.