Build a simple interactive Task Manager UI using PhiaUI components rendered live inside a Livebook notebook. This tutorial shows how to prototype Phoenix LiveView component layouts, explore PhiaUI's visual system, and build interactive demos — all without running a Phoenix server.
What you'll build
- A task list rendered with PhiaUI
tableandbadgecomponents - A quick-add form using
phia_inputandbutton - A stat summary bar with
stat_cardandbadge_delta - Dark mode toggle that affects the notebook preview
How PhiaUI renders in Livebook
PhiaUI uses kino_live_component, a Kino package that runs a minimal Phoenix endpoint inside Livebook. This allows fully styled function components — including Tailwind CSS — to render directly in notebook outputs.
Note:
kino_live_componentrequires Livebook 0.12+ and Elixir 1.17+.
Step 1 — Create a new Livebook notebook
Open Livebook (local or livebook.dev) and create a new notebook named "PhiaUI Task Manager".
Step 2 — Add dependencies
In the first Elixir cell, install PhiaUI and kino_live_component:
Mix.install([
{:phia_ui, "~> 0.1.11"},
{:kino_live_component, "~> 0.0.5"},
{:kino, "~> 0.14"}
])Run the cell. Hex will fetch and compile both packages.
Step 3 — Configure the Kino endpoint
kino_live_component needs a minimal Phoenix endpoint to serve the Tailwind CSS and run component rendering. Add this in the next cell:
defmodule NotebookEndpoint do
use Phoenix.Endpoint, otp_app: :phia_ui_notebook
plug(Plug.Static,
at: "/",
from: {:phia_ui, "priv/static"},
gzip: false
)
socket("/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]]
)
plug(Plug.Session, @session_options)
plug(:router)
def router(conn, _opts), do: conn
@session_options [
store: :cookie,
key: "_phia_key",
signing_salt: "phia_notebook"
]
end
Application.put_env(:phia_ui_notebook, NotebookEndpoint,
url: [host: "localhost"],
secret_key_base: String.duplicate("a", 64),
live_view: [signing_salt: "phia_salt"],
render_errors: [formats: [html: PhiaUi.ErrorHTML], layout: false],
pubsub_server: PhiaUiNotebook.PubSub,
server: false
)
{:ok, _} = Supervisor.start_link([
{Phoenix.PubSub, name: PhiaUiNotebook.PubSub},
NotebookEndpoint
], strategy: :one_for_one)Step 4 — Import PhiaUI components
import PhiaUi.Components.Buttons
import PhiaUi.Components.Inputs
import PhiaUi.Components.Display
import PhiaUi.Components.Feedback
import PhiaUi.Components.Cards
import PhiaUi.Components.DataStep 5 — Render your first component
Use KinoLiveComponent.render/2 to render any PhiaUI component:
alias KinoLiveComponent, as: KLC
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6 space-y-2">
<.badge variant="default">Active</.badge>
<.badge variant="secondary">Draft</.badge>
<.badge variant="destructive">Deleted</.badge>
<.badge variant="outline">Archived</.badge>
</div>
"""
end)You should see four coloured badges rendered inline in the notebook output.
Step 6 — Build the task list
Define some sample tasks and render them in a table:
tasks = [
%{id: 1, title: "Design new landing page", status: "active", priority: "high"},
%{id: 2, title: "Set up CI/CD pipeline", status: "active", priority: "medium"},
%{id: 3, title: "Write API documentation", status: "done", priority: "low"},
%{id: 4, title: "Fix authentication bug", status: "active", priority: "high"},
%{id: 5, title: "Migrate to PostgreSQL 16", status: "backlog", priority: "medium"}
]
status_variant = fn
"active" -> "default"
"done" -> "secondary"
"backlog" -> "outline"
_ -> "outline"
end
priority_variant = fn
"high" -> "destructive"
"medium" -> "secondary"
_ -> "outline"
end
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6">
<h2 class="text-xl font-semibold mb-4">Tasks</h2>
<div class="border rounded-lg overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-muted/50 border-b">
<tr>
<th class="text-left px-4 py-3 font-medium text-muted-foreground">#</th>
<th class="text-left px-4 py-3 font-medium text-muted-foreground">Title</th>
<th class="text-left px-4 py-3 font-medium text-muted-foreground">Status</th>
<th class="text-left px-4 py-3 font-medium text-muted-foreground">Priority</th>
</tr>
</thead>
<tbody class="divide-y">
<%= for task <- tasks do %>
<tr class="hover:bg-muted/30 transition-colors">
<td class="px-4 py-3 text-muted-foreground"><%= task.id %></td>
<td class="px-4 py-3 font-medium"><%= task.title %></td>
<td class="px-4 py-3">
<.badge variant={status_variant.(task.status)}>
<%= String.capitalize(task.status) %>
</.badge>
</td>
<td class="px-4 py-3">
<.badge variant={priority_variant.(task.priority)}>
<%= String.capitalize(task.priority) %>
</.badge>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
"""
end)Step 7 — Add a stat summary bar
active_count = Enum.count(tasks, &(&1.status == "active"))
done_count = Enum.count(tasks, &(&1.status == "done"))
backlog_count = Enum.count(tasks, &(&1.status == "backlog"))
high_priority = Enum.count(tasks, &(&1.priority == "high"))
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6 grid grid-cols-4 gap-4">
<div class="bg-card border rounded-lg p-4">
<p class="text-sm text-muted-foreground">Active</p>
<p class="text-3xl font-bold mt-1"><%= active_count %></p>
</div>
<div class="bg-card border rounded-lg p-4">
<p class="text-sm text-muted-foreground">Completed</p>
<p class="text-3xl font-bold mt-1 text-green-600"><%= done_count %></p>
</div>
<div class="bg-card border rounded-lg p-4">
<p class="text-sm text-muted-foreground">Backlog</p>
<p class="text-3xl font-bold mt-1 text-muted-foreground"><%= backlog_count %></p>
</div>
<div class="bg-card border rounded-lg p-4 border-destructive/30">
<p class="text-sm text-muted-foreground">High Priority</p>
<p class="text-3xl font-bold mt-1 text-destructive"><%= high_priority %></p>
</div>
</div>
"""
end)Step 8 — Add a quick-add form
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6 max-w-lg">
<h3 class="text-lg font-semibold mb-4">Add a task</h3>
<div class="space-y-4 bg-card border rounded-lg p-4">
<div>
<label class="text-sm font-medium block mb-1">Title</label>
<input
type="text"
placeholder="Task title…"
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="text-sm font-medium block mb-1">Status</label>
<select class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm">
<option value="active">Active</option>
<option value="backlog">Backlog</option>
</select>
</div>
<div>
<label class="text-sm font-medium block mb-1">Priority</label>
<select class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm">
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
</div>
<.button variant="default" class="w-full">Add task</.button>
</div>
</div>
"""
end)Note: In Livebook, forms don't fire LiveView events — they are static renders. For interactive forms, move the logic to a real Phoenix LiveView. Use Livebook to prototype the UI, then copy the HEEx template into your LiveView.
Step 9 — Explore other components
Try rendering more PhiaUI components in subsequent cells:
# Alert
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-4 space-y-3">
<.alert variant="success">
<:icon><.icon name="check-circle" /></:icon>
Task completed successfully.
</.alert>
<.alert variant="warning">
<:icon><.icon name="alert-triangle" /></:icon>
3 tasks are overdue.
</.alert>
<.alert variant="destructive">
<:icon><.icon name="x-circle" /></:icon>
Failed to sync with server.
</.alert>
</div>
"""
end)# Progress indicators
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6 space-y-4">
<div>
<div class="flex justify-between text-sm mb-1">
<span>Sprint progress</span>
<span class="text-muted-foreground">60%</span>
</div>
<.progress value={60} />
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span>Storage used</span>
<span class="text-muted-foreground">80%</span>
</div>
<.progress value={80} class="[&>div]:bg-destructive" />
</div>
</div>
"""
end)# Avatar group
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="p-6 flex items-center gap-3">
<.avatar_group max={4}>
<.avatar size="sm"><.avatar_fallback name="Alice Smith" /></.avatar>
<.avatar size="sm"><.avatar_fallback name="Bob Jones" /></.avatar>
<.avatar size="sm"><.avatar_fallback name="Carol White" /></.avatar>
<.avatar size="sm"><.avatar_fallback name="Dave Brown" /></.avatar>
<.avatar size="sm"><.avatar_fallback name="Eve Davis" /></.avatar>
</.avatar_group>
<span class="text-sm text-muted-foreground">5 team members</span>
</div>
"""
end)Step 10 — Dark mode preview
Toggle to dark mode to preview components in both colour schemes:
KLC.render(NotebookEndpoint, fn assigns ->
~H"""
<div class="dark bg-zinc-900 p-6 rounded-lg space-y-4">
<h3 class="text-white font-semibold">Dark mode preview</h3>
<.badge variant="default">Active</.badge>
<.badge variant="secondary">Secondary</.badge>
<div class="bg-card border border-border rounded-lg p-4 mt-3">
<p class="text-foreground">This card uses semantic tokens.</p>
<p class="text-muted-foreground text-sm mt-1">It looks correct in both modes.</p>
</div>
<.button variant="default">Dark mode button</.button>
</div>
"""
end)Next steps
Now that you can render PhiaUI components in Livebook:
- Explore the full component catalog in the Components section of the sidebar
- Build a real LiveView — copy your HEEx templates from the notebook
- Run a tutorial — see Analytics Dashboard or Booking Platform for complete LiveView implementations
Useful patterns when prototyping in Livebook
| Goal | Approach |
|---|---|
| Test a layout | Render with KLC.render/2, iterate in the same cell |
| Try component variants | Render multiple instances side by side in one cell |
| Evaluate dark mode | Wrap the render in <div class="dark bg-zinc-900"> |
| Share a component demo | Export the notebook as .livemd and share the file |
| Move to production | Copy the ~H""" block directly into your LiveView |
Resources
kino_live_componenton Hex — the Kino package used here- Livebook documentation — notebook features and tips
- PhiaUI component catalog — all 829 components with examples
- Phoenix LiveView docs — official LiveView reference