code-quality
Use when refactoring or reviewing Elixir code quality — compiler warnings and Credo first, then the plugin's on-demand…
Use when styling Phoenix 1.8 UI — Tailwind v4 app.css (@source, @plugin, no tailwind.config.js), dynamic classes, daisyUI themes/dark mode, core_components (<.input>, <.button>, <.icon>), Layouts.app, flash, accessibility. Not for LiveView forms/lifecycle or JS hooks (use
$ npx -y skills add j-morgan6/elixir-phoenix-guide --skill phoenix-tailwind-daisyui --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/phoenix-tailwind-daisyuiContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when styling Phoenix 1.8 UI — Tailwind v4 app.css (@source, @plugin, no tailwind.config.js), dynamic classes, daisyUI themes/dark mode, core_components (<.input>, <.button>, <.icon>), Layouts.app, flash, accessibility. Not for LiveView forms/lifecycle or JS hooks (use
name: phoenix-tailwind-daisyui description: "Use when styling Phoenix 1.8 UI — Tailwind v4 app.css (@source, @plugin, no tailwind.config.js), dynamic classes, daisyUI themes/dark mode, core_components (<.input>, <.button>, <.icon>), Layouts.app, flash, accessibility. Not for LiveView forms/lifecycle or JS hooks (use phoenix-liveview-essentials)."
> Verified against Phoenix 1.8, LiveView 1.2, Tailwind 4.3, daisyUI 5.5 — October 2026
1. **Wrap every page in `<Layouts.app flash={@flash}>`**, and pass `current_scope={@current_scope}` when the app has auth. In 1.8 the app layout is a function component that you call from the template; it is not applied automatically. It already renders the flash group, so don't render a second one. 2. **Tailwind is configured in `assets/css/app.css`.** There is no `tailwind.config.js`. Keep `@import "tailwindcss" source(none)`, and add an `@source` line for every directory outside `lib/my_app_web` that contains class names. Tailwind scans only the listed paths. 3. **Write every class name as a complete literal.** `"text-#{color}-500"` produces no CSS. Map values to full strings (`%{error: "badge-error"}`) or use function clauses. Safelist with `@source inline("...")` only when the values really come from data. 4. **Use daisyUI's semantic colors rather than raw palette colors.** Write `bg-base-100`, `text-base-content`, `btn-primary`, `text-error` and the `*-content` pairs, not `bg-white`, `text-gray-900` or `text-red-600`. The semantic colors switch with the light/dark theme; hard-coded colors don't. 5. **Use `core_components.ex` first, and extend it rather than writing one-off markup.** To get a new look, add a variant, an attr or a new component in that module, and keep its `attr`/`slot` declarations, including `values:` and `required:`. 6. **Passing `class` to `<.button>` or `<.input>` replaces the default classes; it doesn't add to them.** `<.button class="w-full">` loses `btn`. Pass the full class list (`class="btn btn-primary w-full"`) or add a variant. 7. **Icons are `<.icon name="hero-…">` with a literal name.** The suffixes are none (outline 24), `-solid`, `-mini` and `-micro`. `icon/1` accepts only `name` and `class`; any other attribute is a compile warning and isn't rendered. 8. **`<.button>` declares only `href navigate patch method download name value disabled`.** `type="button"` (which you need for a non-submitting button inside a form) is an "undefined attribute" warning, and that warning fails `mix precommit` (`compile --warnings-as-errors`). Add `type` to the button's `include:` list. 9. **Accessibility you add yourself:** a `label` on every `<.input>` (a placeholder is not a label), an `aria-label` on every icon-only button, `<button>` or `<.link>` for anything clickable (never `phx-click` on a `div`), and `<.focus_wrap>` around modal content.
Related skills: `phoenix-liveview-essentials` covers forms with `to_form`, streams, JS commands and colocated hooks. `deployment-gotchas` covers `assets.deploy` and the digest in releases.
---
| Piece | Where | What it is | |---|---|---| | Tailwind CLI | `{:tailwind, "~> 0.5"}` in `mix.exs`; `config :tailwind, version: "4.3.3", my_app: [...]` in `config/config.exs` | A standalone binary (no Node) that `mix tailwind.install` downloads to `_build/tailwind-<target>-<version>` | | daisyUI | `{:daisyui, github: "saadeghi/daisyui", tag: "v5.5.20", sparse: "packages/bundle", app: false, compile: false}` in `mix.exs` | A git dep that lands in `deps/daisyui/packages/bundle/`; there is no npm | | Heroicons | `{:heroicons, github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", ...}` | SVGs in `deps/heroicons/optimized`, turned into `hero-*` classes by `assets/vendor/heroicons.js` | | Entry CSS | `assets/css/app.css` | All Tailwind and daisyUI configuration | | Dev watcher | `tailwind: {Tailwind, :install_and_run, [:my_app, ~w(--watch)]}` in `config/dev.exs` | Rebuilds on save | | Aliases | `assets.setup`, `assets.build` (`compile`, `tailwind my_app`, `esbuild my_app`), `assets.deploy` (`tailwind my_app --minify`, `esbuild my_app --minify`, `phx.digest`) | |
`@plugin "daisyui/packages/bundle/daisyui"` resolves because the tailwind profile sets `env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}`, so bare imports are looked up in `deps/` and `_build/<env>/`. To upgrade daisyUI, bump the `tag:` and run `mix deps.update daisyui`. To upgrade Tailwind, bump `version:` in `config.exs` and run `mix tailwind.install`. Apps generated by earlier 1.8.x releases may vendor the plugin as `assets/vendor/daisyui.js` and `daisyui-theme.js` and point `@plugin` at `../vendor/...` instead. Check which form your app uses before you edit anything.
@import "tailwindcss" source(none); /* turn OFF automatic source detection */
@import "phoenix-colocated/my_app/colocated.css"; /* LiveView ColocatedCSS output (in _build) */
@source "../css";
@source "../js"; /* classes toggled from JS */
@source "../../lib/my_app_web"; /* every HEEx template and component */
@source "../../_build/dev/phoenix-colocated/my_app/*/"; /* dev reload of colocated CSS */
@plugin "../vendor/heroicons"; /* hero-* classes */
@plugin "daisyui/packages/bundle/daisyui" { themes: false; } /* components, no built-in themes */
@plugin "daisyui/packages/bundle/daisyui-theme" { name: "dark"; prefersdark: true; /* --color-* */ }
@plugin "daisyui/packages/bundle/daisyui-theme" { name: "light"; default: true; /* --color-* */ }
@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
@custom-variant· Changelog A Claude Code plugin (with Codex support for the skills) that keeps Claude's Elixir and Phoenix code idiomatic and current.
Use when refactoring or reviewing Elixir code quality — compiler warnings and Credo first, then the plugin's on-demand…
Use when preparing releases or deployment config — mix phx.gen.release, runtime.exs vs compile-time config, release migrations,…
Use when writing Ecto changeset validations beyond cast/validate_required — custom and cross-field validations (e.g. end date…
Use when defining schemas, writing queries, or creating migrations — scoped context functions, Repo.transact, upserts, indexes,…
Use when migrating PostgreSQL tables that serve live traffic — index concurrently,…
Use when one form or operation manages parent and child records together — cast_assoc/cast_embed, inputs_for with sort/drop…