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.
- It separates metadata, view resolution, recipes, and render trees into clear layers.
- It gives humans and agents a smaller authoring surface through standalone UI modules.
- It keeps generated screens, custom layouts, and live runtime components on one contract.
- It can grow from generated pages into product UI without forcing a rewrite of the whole stack.
When to Use AshSDUI
AshSDUI is designed as an authoring ladder for Ash-backed LiveView applications.
Use it when you want:
- repeated Ash-backed screens that share field, form, action, and query metadata
- generated or semi-generated forms with relationship selectors and nested forms
- custom page shells that still keep data loading on one runtime contract
- server-driven layouts that vary by actor, tenant, audience, device, or runtime state
- dynamic, ephemeral, or persisted layout trees
- a smaller, safer authoring surface for humans and LLM agents
Prefer raw LiveView when you are building:
- one-off bespoke pages with little reusable resource metadata
- interaction-heavy product UI with custom event flows at every layer
- screens whose main complexity is not forms, actions, queries, or dynamic layout and reuse
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
- Metadata-driven generated screens through
AshSDUI.LiveResource - A shared runtime contract for generated views and SDUI layouts:
view,bindings,state, andcontext - Layout authoring with
AshSDUI.Layout.Builderplus persisted layouts throughAshSDUI.Layout - Ephemeral runtime layouts through
AshSDUI.LiveScreen.assign_layout/3 - Metadata-driven forms and actions from
ui_field,ui_nested_form,ui_attribute, andui_intent - Live bindings with poll, PubSub, and stream-style update paths
- Reusable runtime-aware components for lists, metrics, status, activity, and selection
- Storybook and demo surfaces that exercise the generated and layout-rendered paths
- ETS-backed layout caching with automatic invalidation for stored-node changes
Installation
def deps do
[
{:ash_sdui, "~> 0.1"},
{:phoenix_live_view, "~> 1"}
]
endThe 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:
- define UI metadata for an Ash resource
- mount it with
AshSDUI.LiveResource - 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
endscope "/", MyAppWeb do
pipe_through :browser
live "/posts", PostsLive
live "/posts/new", PostNewLive
endThis gives you:
- a generated collection screen at
/posts - a generated form screen at
/posts/new - form widgets driven by
ui_fieldmetadata - action labels and targets driven by
ui_intentmetadata - room to add relationship-aware widgets such as generated selects without replacing the generated host
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: 2When :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
endThat keeps relationship picking on ui_field and inline related-record editing
on ui_nested_form.
For layout authoring, prefer:
AshSDUI.Layout.Builder.resource/2andresources/3AshSDUI.Layout.fetch/2,register/2,save/3, andpublish/2AshSDUI.LiveScreen.assign_layout/3
Avoid new code that depends on AshSDUI.Layout.Persistence directly.
Documentation
Tutorial
How-to Guides
- Author Generated Screens
- Customize Generated Forms
- Use Queries and Filters
- Add Live Bindings
- Render Generated Views in Storybook
- Use
layout: :sduiRecipes - Build Nested Layouts
- Work with SDUI Layouts
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.