Getting Started

View Source

This guide walks through adding Phoenix Duskmoon UI to a Phoenix LiveView project.

Prerequisites

  • Elixir ~> 1.15
  • Phoenix ~> 1.8.1
  • Phoenix LiveView ~> 1.1.0
  • TailwindCSS >= 4.0
  • Bun (for frontend package management)

Installation

1. Add the Hex dependency

# mix.exs
defp deps do
  [
    {:phoenix_duskmoon, "~> 9.0"}
  ]
end
mix deps.get

2. Install frontend packages

bun add @duskmoon-dev/core @duskmoon-dev/elements

@duskmoon-dev/core provides the CSS design system (theme variables, component classes, design tokens). @duskmoon-dev/elements is a meta-package that installs all el-dm-* custom element packages.

3. Import components in your view helpers

In your lib/my_app_web.ex, add the component imports to html_helpers/0:

defp html_helpers do
  quote do
    # Import all Duskmoon UI components (dm_btn, dm_card, dm_input, etc.)
    use PhoenixDuskmoon.Component

    # Import CSS art components (dm_art_snow, dm_art_plasma_ball, etc.)
    use PhoenixDuskmoon.ArtComponent

    # ... your other imports
  end
end

4. Configure CSS

In your assets/css/app.css:

@source "../js/**/*.js";
@source "../../lib/**/*.exs";
@source "../../lib/**/*.ex";

@import "tailwindcss";
@plugin "@duskmoon-dev/core/plugin";

The @plugin directive registers the Duskmoon design system with Tailwind v4, providing all theme variables, component classes, and design tokens.

5. Register custom elements

In your assets/js/app.js, import the element packages you use:

// Register individual elements
import "@duskmoon-dev/el-button/register";
import "@duskmoon-dev/el-card/register";
import "@duskmoon-dev/el-input/register";
// ... add more as needed

// Or register all elements at once
import "@duskmoon-dev/elements/register";

Each custom element must be explicitly registered for its tag to be defined in the browser. Without registration, content inside the element's <template> is inert and invisible.

6. Set up LiveView hooks

Some components require JavaScript hooks for client-side interactions. Import and register them with your LiveSocket:

import { LiveSocket } from "phoenix_live_view";
import * as DuskmoonHooks from "phoenix_duskmoon/hooks";

let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  hooks: DuskmoonHooks,
});

To merge with your own hooks:

import * as DuskmoonHooks from "phoenix_duskmoon/hooks";
import MyHooks from "./my_hooks";

let hooks = { ...DuskmoonHooks, ...MyHooks };

let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  hooks: hooks,
});

See the Hooks guide for details on each hook.

Usage

All components use the dm_ prefix:

<%!-- Buttons --%>
<.dm_btn variant="primary">Save</.dm_btn>
<.dm_btn variant="error" shape="circle">×</.dm_btn>

<%!-- Cards with slots --%>
<.dm_card>
  <:title>Card Title</:title>
  Content goes here
  <:footer><.dm_btn variant="primary">Action</.dm_btn></:footer>
</.dm_card>

<%!-- Icons --%>
<.dm_mdi name="home" />
<.dm_bsi name="house" />

<%!-- Forms with field binding --%>
<.dm_form for={@form} phx-submit="save">
  <.dm_input field={@form[:name]} label="Name" />
  <.dm_select field={@form[:role]} label="Role" options={[{"admin", "Admin"}, {"user", "User"}]} />
  <.dm_btn variant="primary" type="submit">Save</.dm_btn>
</.dm_form>

Common attributes

AttributeValuesDescription
variantprimary, secondary, accent, info, success, warning, error, ghost, link, outlineColor variant
sizexs, sm, md, lgSize variant
shapesquare, circleShape variant
loadingbooleanShows loading spinner
disabledbooleanDisables interaction
classstring or listAdditional CSS classes

Form field binding

Data entry components support direct Phoenix.HTML.FormField binding via the field attribute, which automatically sets name, id, value, and error state:

<.dm_input field={@form[:email]} label="Email" type="email" />
<.dm_checkbox field={@form[:terms]} label="I agree to the terms" />
<.dm_switch field={@form[:notifications]} label="Enable notifications" />