Shared UI + its event handlers in one module — for drawers, pickers, and other components used across many pages.
defmodule MyAppWeb.DetailDrawer do
use Dstar.Component
def drawer(assigns) do
~H"""
<div id="detail-drawer">
<input data-on:change={event("change_title:#{@item.id}")} />
</div>
"""
end
def handle_event(conn, "change_title:" <> id, signals) do
# ... update, then patch
conn |> start() |> patch_signals(%{saved: true})
end
endPages embed the UI as plain function components and need no
handle_event clauses for it — events route to this module via
Dstar.Plugs.Dispatch (wire it with Dstar.Router.dstar_components/2).
Unlike Dstar.Page, event/2 here targets the component's dispatch
URL: <base>/<encoded-module>/<event>. The base defaults to /ds and
is read client-side from <body data-ds-base="...">, so it must match
the base you give dstar_components/2. Two common setups:
# Router: dstar_components "/ds", [...] (the default — no layout
# attribute needed)
# Custom base and/or app path prefix — declare it once in the root
# layout, including the dispatch segment:
# Router: scope "/:workspace_slug" do dstar_components "/ds", [...] end
<body data-ds-base={workspace_path(@current_scope.workspace.slug) <> "/ds"}>Colocation only: no server-side component state, no lifecycle. State lives in signals, the DOM, and the database.