AshSDUI

AshSDUI is a server-driven UI layer for Phoenix LiveView applications backed by Ash. It combines metadata-driven generated screens, persisted or code-authored layout trees, and a shared runtime contract for building Ash-aware LiveView interfaces.

Why AshSDUI

AshSDUI is useful when you want more than scaffolded CRUD, but you still want a declarative path that stays close to your Ash resource model.

When to Use AshSDUI

AshSDUI is designed as an authoring ladder for Ash-backed LiveView applications.

Use it when you want:

Prefer raw LiveView when you are building:

Rule of thumb: AshSDUI starts paying off when several screens share resource metadata such as fields, actions, queries, relationships, bindings, or layout structure. For one highly custom page, raw LiveView may be simpler. For repeated Ash-backed UI, AshSDUI reduces drift and boilerplate.

See When AshSDUI Pays Off for a grounded comparison with demo-backed LOC ranges and adoption guidance.

Features

Installation

def deps do
[
{:ash_sdui, "~> 0.1"},
{:phoenix_live_view, "~> 1"}
]
end

The built-in AshSDUI.UINode uses ETS storage and is suitable for tests, demos, and local prototypes. Production applications that need database-backed layouts should provide a compatible Ash resource and pass it as node_resource:.

Quickstart

The easiest end-to-end path is:

  1. define UI metadata for an Ash resource
  2. mount it with AshSDUI.LiveResource
  3. expose the generated screens from your router
defmodule MyApp.UI.PostUI do
use AshSDUI.Resource.Standalone
sdui do
for_resource MyApp.Blog.Post
view :index, recipe: :collection, read_action: :read
view :new, recipe: :form, action: :create
ui_field :title, label: "Headline", widget: :text_input, order: 0
ui_field :body, label: "Body", widget: :textarea, order: 1
ui_field :author_id, label: "Author", order: 2
ui_intent :create,
label: "Write post",
target: {:navigate, "/posts/new"}
end
end
defmodule MyAppWeb.PostsLive do
use AshSDUI.LiveResource,
ui: MyApp.UI.PostUI,
view: :index,
domain: MyApp.Blog
end
defmodule MyAppWeb.PostNewLive do
use AshSDUI.LiveResource,
ui: MyApp.UI.PostUI,
view: :new,
domain: MyApp.Blog
end
scope "/", MyAppWeb do
pipe_through :browser
live "/posts", PostsLive
live "/posts/new", PostNewLive
end

This gives you:

Prefer this path before stepping up to custom recipes or hand-authored LiveViews.

If a generated form should let the user pick an existing related record, keep that in metadata too:

ui_field :author_id,
label: "Author",
order: 2

When :author_id matches a belongs_to relationship source attribute such as author, the generated form now renders a select automatically and loads the options from the related resource's read action. For relationship arguments such as :tag_ids, use relationship: and let the form render a multiselect.

If the form should create or edit related records inline, model that separately with ui_nested_form:

create :create do
accept [:title]
argument :comments, {:array, :map}, allow_nil?: true
change manage_relationship(:comments, :comments, type: :direct_control)
end
sdui do
ui_field :title, label: "Headline", order: 1
ui_nested_form :comments, label: "Comments", order: 2
end

That keeps relationship picking on ui_field and inline related-record editing on ui_nested_form.

For layout authoring, prefer:

Avoid new code that depends on AshSDUI.Layout.Persistence directly.

Documentation

Tutorial

How-to Guides

Reference

Explanation

Demo and proof surfaces

examples/sdui_demo is the public proof surface for promoted features. It maps generated screens, runtime bindings, hybrid layouts, and persisted layouts to a demo route, Storybook surface, and regression test.

Storybook is part of that proof surface. Prefer generated-view and reusable building-block stories over raw low-level component stories when documenting or reviewing package behavior.

See examples/sdui_demo/README.md for the current coverage matrix.