Ejects a PhiaUI component into the current Phoenix project as an editable source file.
When you run mix phia.add, PhiaUI renders an EEx template and writes the
result to lib/{app_web}/components/ui/{component}.ex. From that point on,
the file belongs to your project — edit it freely. This is the same
"copy-paste ownership" model popularised by shadcn/ui.
Usage
mix phia.add <component>Available components
button badge card table dialogWhat gets created
Running mix phia.add button in a project called my_app creates:
lib/my_app_web/components/ui/button.ex
lib/my_app_web/class_merger.ex (shared dependency, skipped if exists)
lib/my_app_web/class_merger/cache.ex (shared dependency, skipped if exists)
lib/my_app_web/class_merger/groups.ex (shared dependency, skipped if exists)
assets/js/phia_hooks/{component}.js (only for components that need a hook)The web module namespace (e.g., MyAppWeb) is derived automatically from the
:app key in mix.exs.
Behaviour
- Derives the web module name from
Mix.Project.config()[:app](e.g.,:my_app→MyAppWeb). - Renders the EEx template from PhiaUI's
priv/templates/components/. - Writes the file with
Mix.Generator.create_file/3(conflict-aware). - Idempotent: if the file already exists the user is prompted before any overwrite.
- The
ClassMergerutility (required by every component) is ejected automatically and skipped if already present. - Prints a confirmation message via
Mix.shell().info/1.
Next steps after ejecting
- Run
mix phia.installonce (if you haven't already) to inject PhiaUI theme tokens intoassets/css/app.css. - Add
ClassMerger.Cacheto your supervision tree: children = [MyAppWeb.ClassMerger.Cache, ...] - Import the component in any LiveView or layout: import MyAppWeb.Components.UI.Button
Options
--help Print this help messageExamples
$ mix phia.add button
* create lib/my_app_web/components/ui/button.ex
$ mix phia.add dialog
* create lib/my_app_web/components/ui/dialog.ex
* create assets/js/phia_hooks/dialog.js
Summary
Functions
Derives the host project's web module name from the current Mix project.
Builds the absolute target file path for a component under root.
Ejects the ClassMerger dependency files into root.
Ejects a component from PhiaUI's templates into root.
Copies the JS hook file for component_name into root/assets/js/phia_hooks/
if a corresponding hook template exists at priv/templates/js/hooks/{component}.js.
Returns a next-steps message to display after ejecting component_name.
Functions
@spec app_web_name() :: String.t()
Derives the host project's web module name from the current Mix project.
The web module name is the camelized app name suffixed with "Web". For
example, a project with app: :my_app returns "MyAppWeb".
Example
iex> Mix.Tasks.Phia.Add.app_web_name()
"PhiaUiWeb"
Builds the absolute target file path for a component under root.
The path follows the Phoenix convention of placing UI components under
lib/{app_web_dir}/components/ui/.
Example
iex> Mix.Tasks.Phia.Add.component_path("/project", "button")
"/project/lib/phia_ui_web/components/ui/button.ex"
@spec eject_class_merger(Path.t()) :: :ok
Ejects the ClassMerger dependency files into root.
ClassMerger is a Tailwind CSS class conflict resolver (similar to
tailwind-merge) implemented in pure Elixir. Every PhiaUI component imports
the cn/1 helper it provides, so it must be present in the host project.
Creates three files relative to lib/{app_web_dir}/:
class_merger.ex— maincn/1functionclass_merger/cache.ex— ETS-backed GenServer cacheclass_merger/groups.ex— Tailwind conflict group mappings
Skips any file that already exists, making this function safe to call repeatedly.
Ejects a component from PhiaUI's templates into root.
This is the main entry point called by run/1. It runs a pipeline of
steps: validate → resolve_target → render_content → write_file.
Also automatically ejects the ClassMerger utility (a required dependency
shared by all components) unless it is already present.
Return values
:ok— component was created successfully.:already_exists— file already existed; user was prompted.{:error, reason}— component name is not in the known components list.
Example
iex> Mix.Tasks.Phia.Add.eject_component("button", "/tmp/my_project")
:ok
Copies the JS hook file for component_name into root/assets/js/phia_hooks/
if a corresponding hook template exists at priv/templates/js/hooks/{component}.js.
Components such as Dialog, DropdownMenu, and Toast require a client-side JavaScript hook for browser interactions (focus trapping, keyboard handling, etc.). This function handles that copy automatically during ejection.
No-op (returns :ok) when no hook template is found for the given component.
Idempotent: skips the copy if the target hook file already exists.
Returns a next-steps message to display after ejecting component_name.
This message reminds the developer to run mix phia.install, add
ClassMerger.Cache to their supervision tree, and import the component.