PhiaUi (phia_ui v0.1.17)

Copy Markdown View Source

PhiaUI — a shadcn/ui-inspired component library for Phoenix LiveView.

PhiaUI brings the composable, anatomy-driven design system of shadcn/ui to the Phoenix ecosystem. Every component is a stateless HEEx function component that integrates with Phoenix.HTML.Form, Ecto changesets, and LiveView's JS command system. There are no npm dependencies and no Alpine.js. JS hooks (where required) are plain vanilla JavaScript files that you own after ejection.

Design Principles

  1. Copy-paste ownership — run mix phia.add ComponentName and the source file is yours; PhiaUI never regenerates it.
  2. Composition over configuration — small, single-purpose components that you assemble rather than a single mega-component with dozens of props.
  3. Accessibility first — every interactive component implements the relevant WAI-ARIA pattern (roles, states, keyboard navigation).
  4. Semantic Tailwind v4 tokens — uses --color-primary, --radius, etc. from priv/static/theme.css; never hard-codes hex values.
  5. Zero JS dependencies — hooks are vanilla JavaScript (~50–150 LOC each) copied into your assets/js/hooks/ directory.
  6. LiveView-native — state lives in the LiveView process; hooks only handle DOM-level behaviour (focus traps, pointer events, scroll).
  7. Dark mode first — every component ships with dark: variants driven by the .dark class on <html>, toggled by PhiaDarkMode.
  8. Beautiful defaults, fully customisable — sensible out-of-the-box Tailwind classes, overridable via the class attribute on every component.
  9. Enterprise by default — DataGrid, KanbanBoard, FilterBuilder, and ActivityFeed are production-ready, not demo toys.
  10. Elixir-idiomatic — no macros, no metaprogramming; just plain function components and straightforward pattern matching.

Architecture

PhiaUI is organised into eight layers:

1. Primitive Components

Stateless, zero-JS layout and display components. Safe to use in any context, including dead views and email templates.

2. Form Integration

Fully integrated with Phoenix.HTML.FormField and Ecto changeset errors. All form components forward field errors automatically; no manual wiring.

3. Interactive Components

WAI-ARIA compliant components backed by lightweight vanilla JS hooks. Each hook is self-contained (~50–150 LOC) and has no external dependencies.

4. Navigation and Tabs

Structural navigation patterns for multi-page and multi-section UIs.

5. Dashboard Shell

Enterprise-grade layout shell using CSS Grid (grid-cols-[240px_1fr] h-screen) on desktop and a Flexbox drawer on mobile. The shell is self-contained and requires no external CSS.

  • PhiaUi.Components.Shellshell/1 — outer page grid; optional collapsible sidebar
  • PhiaUi.Components.Shellsidebar/1:variant (:default / :dark); sidebar_item/1 with :badge attr and :icon slot; sidebar_section/1 for grouped nav links
  • PhiaUi.Components.Shelltopbar/1 — sticky top bar with left/right action slots; mobile_sidebar_toggle/1
  • PhiaUi.Components.DarkModeToggledark_mode_toggle/1 — reads prefers-color-scheme, persists to localStorage['phia-mode'], toggles .dark on <html> (PhiaDarkMode hook)
  • PhiaUi.Components.ThemeProvidertheme_provider/1 — sets data-phia-theme on a wrapper element; reads preset from localStorage['phia-color-theme'] via PhiaTheme hook. Requires phia-themes.css generated by mix phia.theme install

6. Dashboard Widgets

Composed analytics widgets for BI dashboards and KPI monitors.

  • PhiaUi.Components.StatCardstat_card/1 — trend indicator (up/down arrow), icon slot, footer slot; shadow-sm tracking-tight polish
  • PhiaUi.Components.MetricGridmetric_grid/1 — responsive 1–4 column grid that wraps stat_card/1 children
  • PhiaUi.Components.ChartShellchart_shell/1 — titled card wrapper for any charting library; description and actions slots
  • PhiaUi.Components.Chartchart/1PhiaChart hook; auto-detects window.echarts (Apache ECharts) or window.Chart (Chart.js); passes config and series via data-config / data-series JSON attributes
  • PhiaUi.Components.DataGriddata_grid/1 — sortable columns via phx-click + phx-value; streams-compatible; sort direction cycles asc → desc → none

7. Enterprise Components

Production-ready components for complex data workflows and collaboration UIs.

8. Utility Components

Date pickers, carousels, and layout utilities used across multiple contexts.

Installation

Add PhiaUI to your mix.exs dependencies:

def deps do
  [
    {:phia_ui, "~> 0.1"}
  ]
end

Then run the installer to copy hooks and the Tailwind theme into your project:

mix phia.install

Install the optional theme CSS for multi-theme support:

mix phia.theme install

This generates assets/css/phia-themes.css (all 8 presets × 2 selectors) and injects an @import into assets/css/app.css idempotently.

To add individual components as ejectable source files:

mix phia.add Button
mix phia.add Card Badge Table

Quick Start

A minimal LiveView using several PhiaUI components:

defmodule MyAppWeb.DemoLive do
  use MyAppWeb, :live_view

  import PhiaUi.Components.Button
  import PhiaUi.Components.Card
  import PhiaUi.Components.Badge
  import PhiaUi.Components.Input

  def render(assigns) do
    ~H"""
    <.card>
      <.card_header>
        <.card_title>Welcome</.card_title>
        <.card_description>
          Manage your account settings.
          <.badge variant={:secondary}>Beta</.badge>
        </.card_description>
      </.card_header>
      <.card_content>
        <.form for={@form} phx-change="validate" phx-submit="save">
          <.phia_input field={@form[:email]} label="Email" phx-debounce="blur" />
          <.button type="submit" class="mt-4">Save</.button>
        </.form>
      </.card_content>
    </.card>
    """
  end
end

A dashboard LiveView using the shell layout with a sidebar:

defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  import PhiaUi.Components.Shell
  import PhiaUi.Components.StatCard
  import PhiaUi.Components.MetricGrid

  def render(assigns) do
    ~H"""
    <.shell>
      <:sidebar>
        <.sidebar>
          <.sidebar_section label="Main">
            <.sidebar_item icon="home" label="Dashboard" href="/dashboard" active />
            <.sidebar_item icon="users" label="Team" href="/team" />
          </.sidebar_section>
        </.sidebar>
      </:sidebar>
      <:main>
        <.topbar title="Dashboard" />
        <.metric_grid>
          <.stat_card title="MRR" value="$12,400" trend="+8%" trend_direction={:up} />
          <.stat_card title="DAU" value="3,210" trend="-2%" trend_direction={:down} />
        </.metric_grid>
      </:main>
    </.shell>
    """
  end
end

ClassMerger / cn/1

PhiaUi.ClassMerger.cn/1 is PhiaUI's Tailwind class conflict resolver — equivalent to clsx + tailwind-merge in the JS ecosystem, implemented natively in Elixir without any external dependencies.

Pass a list of class strings (or nil / false values, which are ignored). When two classes target the same Tailwind utility group (e.g. px-2 and px-4), the last one wins:

cn(["px-2 py-1", nil, "px-4"])
#=> "py-1 px-4"

Every component accepts a :class attribute that is merged through cn/1, so you can safely override individual utilities without worrying about duplication:

<.button class="px-8">Wide Button</.button>

Theme System

PhiaUI ships with 8 built-in colour presets: zinc, slate, blue, rose, orange, green, violet, and neutral. The active theme is set by placing a data-phia-theme attribute on any ancestor element (or <html> for a global theme).

Runtime theme switching (colour preset)

Add the PhiaTheme hook to a button or <select> with a data-theme attribute, and the hook will update data-phia-theme on <html> and persist the selection to localStorage['phia-color-theme']:

<button data-theme="rose" phx-hook="PhiaTheme" id="theme-rose">
  Rose
</button>

Anti-FOUC setup

Add the following snippet to the <head> of your root.html.heex before any stylesheet links to restore both dark mode and colour preset without a flash of unstyled content:

<script>
  (function(){
    var m = localStorage.getItem('phia-mode') || localStorage.getItem('phia-theme');
    if (m === 'dark') document.documentElement.classList.add('dark');
    var t = localStorage.getItem('phia-color-theme');
    if (t) document.documentElement.setAttribute('data-phia-theme', t);
  })();
</script>

JS Hooks

The following components use vanilla JS hooks for accessible behaviour that cannot be achieved with Phoenix.LiveView.JS alone. After running mix phia.install, all hook files are copied into your assets/js/hooks/ directory and wired into your LiveSocket automatically.

HookComponent(s)
PhiaDialogDialog, AlertDialog, Sheet
PhiaDropdownMenuDropdownMenu
PhiaTagsInputTagsInput
PhiaRichTextEditorRichTextEditor
PhiaTooltipTooltip
PhiaPopoverPopover, DatePicker
PhiaToastToast
PhiaDarkModeDarkModeToggle
PhiaThemeThemeProvider
PhiaCommandCommand
PhiaCalendarCalendar
PhiaCarouselCarousel
PhiaContextMenuContextMenu
PhiaDrawerDrawer
PhiaChartChart
PhiaDateRangePickerDateRangePicker
PhiaMentionInputMentionInput
PhiaResizableResizable
PhiaKanbanKanbanBoard

Ejectable Architecture

Every component can be ejected — copied as editable source into your project — so you always own the code:

mix phia.add Button Card Badge

Ejected files appear under lib/your_app_web/components/ and are fully independent of the PhiaUI package. You can modify them freely without upgrading or forking. This is the same model as shadcn/ui: PhiaUI is a distribution mechanism, not a runtime dependency you are locked into.