diff --git a/guides/mix_tasks.md b/guides/mix_tasks.md
index 8abe3594ae..62900d7374 100644
--- a/guides/mix_tasks.md
+++ b/guides/mix_tasks.md
@@ -419,10 +419,10 @@ And then `assets/` which should look similar to this:
```console
├── css
-│ └── app.css
+│ ├── app.css
+│ └── tailwind_heroicons.js
├── js
│ └── app.js
-├── tailwind.config.js
└── vendor
└── topbar.js
```
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index 96d389117f..e4761b294d 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -70,7 +70,7 @@ defmodule Phx.New.Single do
template(:css, [
{:eex, :web,
"phx_assets/app.css": "assets/css/app.css",
- "phx_assets/tailwind.config.js": "assets/tailwind.config.js"}
+ "phx_assets/tailwind_heroicons.js": "assets/css/tailwind_heroicons.js"}
])
template(:js, [
diff --git a/installer/templates/phx_assets/app.css b/installer/templates/phx_assets/app.css
index 5b7776c97c..e9a997efb7 100644
--- a/installer/templates/phx_assets/app.css
+++ b/installer/templates/phx_assets/app.css
@@ -1,6 +1,15 @@
-@import "tailwindcss/base";
-@import "tailwindcss/components";
-@import "tailwindcss/utilities";
+/* See the Tailwind configuration guide for advanced usage
+ https://tailwindcss.com/docs/configuration */
+
+@import "tailwindcss";
+@plugin "@tailwindcss/forms";
+@plugin "./tailwind_heroicons.js";
+@variant phx-click-loading ([".phx-click-loading&", ".phx-click-loading &"]);
+@variant phx-submit-loading ([".phx-submit-loading&", ".phx-submit-loading &"]);
+@variant phx-change-loading ([".phx-change-loading&", ".phx-change-loading &"]);
+@theme {
+ --color-brand: "#FD4F00",
+};
/*
* Make LiveView wrapper divs transparent for layout.
diff --git a/installer/templates/phx_assets/tailwind.config.js b/installer/templates/phx_assets/tailwind.config.js
deleted file mode 100644
index 5c65c09145..0000000000
--- a/installer/templates/phx_assets/tailwind.config.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// See the Tailwind configuration guide for advanced usage
-// https://tailwindcss.com/docs/configuration
-
-const plugin = require("tailwindcss/plugin")
-const fs = require("fs")
-const path = require("path")
-
-module.exports = {
- content: [
- "./js/**/*.js",
- "../lib/<%= @lib_web_name || @app_name %>.ex",
- "../lib/<%= @lib_web_name || @app_name %>/**/*.*ex"
- ],
- theme: {
- extend: {
- colors: {
- brand: "#FD4F00",
- }
- },
- },
- plugins: [
- require("@tailwindcss/forms"),
- // Allows prefixing tailwind classes with LiveView classes to add rules
- // only when LiveView classes are applied, for example:
- //
- //
- //
- plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
- plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
- plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
-
- // Embeds Heroicons (https://heroicons.com) into your app.css bundle
- // See your `CoreComponents.icon/1` for more information.
- //
- plugin(function({matchComponents, theme}) {
- let iconsDir = path.join(__dirname, "..<%= if @in_umbrella, do: "/../.." %>/deps/heroicons/optimized")
- let values = {}
- let icons = [
- ["", "/24/outline"],
- ["-solid", "/24/solid"],
- ["-mini", "/20/solid"],
- ["-micro", "/16/solid"]
- ]
- icons.forEach(([suffix, dir]) => {
- fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
- let name = path.basename(file, ".svg") + suffix
- values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
- })
- })
- matchComponents({
- "hero": ({name, fullPath}) => {
- let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
- content = encodeURIComponent(content)
- let size = theme("spacing.6")
- if (name.endsWith("-mini")) {
- size = theme("spacing.5")
- } else if (name.endsWith("-micro")) {
- size = theme("spacing.4")
- }
- return {
- [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
- "-webkit-mask": `var(--hero-${name})`,
- "mask": `var(--hero-${name})`,
- "mask-repeat": "no-repeat",
- "background-color": "currentColor",
- "vertical-align": "middle",
- "display": "inline-block",
- "width": size,
- "height": size
- }
- }
- }, {values})
- })
- ]
-}
diff --git a/installer/templates/phx_assets/tailwind_heroicons.js b/installer/templates/phx_assets/tailwind_heroicons.js
new file mode 100644
index 0000000000..98b4e7e4fe
--- /dev/null
+++ b/installer/templates/phx_assets/tailwind_heroicons.js
@@ -0,0 +1,43 @@
+const plugin = require("tailwindcss/plugin")
+const fs = require("fs")
+const path = require("path")
+
+module.exports = plugin(function({matchComponents, theme}) {
+ let iconsDir = path.join(__dirname, "../..<%= if @in_umbrella, do: "/../.." %>/deps/heroicons/optimized")
+ let values = {}
+ let icons = [
+ ["", "/24/outline"],
+ ["-solid", "/24/solid"],
+ ["-mini", "/20/solid"],
+ ["-micro", "/16/solid"]
+ ]
+ icons.forEach(([suffix, dir]) => {
+ fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
+ let name = path.basename(file, ".svg") + suffix
+ values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
+ })
+ })
+ matchComponents({
+ "hero": ({name, fullPath}) => {
+ let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
+ content = encodeURIComponent(content)
+ let size = theme("spacing.6")
+ if (name.endsWith("-mini")) {
+ size = theme("spacing.5")
+ } else if (name.endsWith("-micro")) {
+ size = theme("spacing.4")
+ }
+ return {
+ [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
+ "-webkit-mask": `var(--hero-${name})`,
+ "mask": `var(--hero-${name})`,
+ "mask-repeat": "no-repeat",
+ "background-color": "currentColor",
+ "vertical-align": "middle",
+ "display": "inline-block",
+ "width": size,
+ "height": size
+ }
+ }
+ }, {values})
+})
diff --git a/installer/templates/phx_single/config/config.exs b/installer/templates/phx_single/config/config.exs
index 97c1bbf53b..8422bbb6a9 100644
--- a/installer/templates/phx_single/config/config.exs
+++ b/installer/templates/phx_single/config/config.exs
@@ -45,14 +45,13 @@ config :esbuild,
# Configure tailwind (the version is required)
config :tailwind,
- version: "3.4.3",
+ version: "4.0.3",
<%= @app_name %>: [
args: ~w(
- --config=tailwind.config.js
- --input=css/app.css
- --output=../priv/static/assets/app.css
+ --input=assets/css/app.css
+ --output=priv/static/assets/app.css
),
- cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>/assets", __DIR__),
+ cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>", __DIR__),
]<% end %>
# Configures Elixir's Logger
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index 0e990e8d0c..2464c7be0b 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -47,7 +47,7 @@ defmodule <%= @app_module %>.MixProject do
{:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %>
{:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %>
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %>
- {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
+ {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
index 2b2f6300d0..a12cc9bc29 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
@@ -29,12 +29,11 @@ config :esbuild,
# Configure tailwind (the version is required)
config :tailwind,
- version: "3.4.3",
+ version: "4.0.3",
<%= @web_app_name %>: [
args: ~w(
- --config=tailwind.config.js
- --input=css/app.css
- --output=../priv/static/assets/app.css
+ --input=assets/css/app.css
+ --output=priv/static/assets/app.css
),
- cd: Path.expand("../apps/<%= @web_app_name %>/assets", __DIR__)
+ cd: Path.expand("../apps/<%= @web_app_name %>", __DIR__)
]<% end %>
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index d2ce376fcd..cfeede37bf 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -45,7 +45,7 @@ defmodule <%= @web_namespace %>.MixProject do
{:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %>
{:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %>
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %>
- {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
+ {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex
index f5ba025411..2708586066 100644
--- a/installer/templates/phx_web/components/core_components.ex
+++ b/installer/templates/phx_web/components/core_components.ex
@@ -520,7 +520,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
width, height, and background color classes.
Icons are extracted from the `deps/heroicons` directory and bundled within
- your compiled app.css by the plugin in your `assets/tailwind.config.js`.
+ your compiled app.css by the plugin in `assets/css/tailwind_heroicons.js`.
## Examples
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index 9690c7b66b..0605944a45 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -154,11 +154,6 @@ defmodule Mix.Tasks.Phx.NewTest do
# tailwind
assert_file("phx_blog/assets/css/app.css")
- assert_file("phx_blog/assets/tailwind.config.js", fn file ->
- assert file =~ "phx_blog_web.ex"
- assert file =~ "phx_blog_web/**/*.*ex"
- end)
-
refute File.exists?("phx_blog/priv/static/assets/app.css")
refute File.exists?("phx_blog/priv/static/assets/app.js")
assert File.exists?("phx_blog/assets/vendor")
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 31a95ed3a4..a9315308af 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -183,11 +183,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
assert_file(web_path(@app, ".gitignore"), ~r/\n$/)
assert_file(web_path(@app, "assets/css/app.css"))
- assert_file(web_path(@app, "assets/tailwind.config.js"), fn file ->
- assert file =~ "phx_umb_web.ex"
- assert file =~ "phx_umb_web/**/*.*ex"
- end)
-
assert_file(web_path(@app, "priv/static/favicon.ico"))
refute File.exists?(web_path(@app, "priv/static/assets/app.css"))
diff --git a/installer/test/phx_new_web_test.exs b/installer/test/phx_new_web_test.exs
index 9911ce22c0..2f13f4cda9 100644
--- a/installer/test/phx_new_web_test.exs
+++ b/installer/test/phx_new_web_test.exs
@@ -63,15 +63,4 @@ defmodule Mix.Tasks.Phx.New.WebTest do
assert_received {:mix_shell, :info, ["Start your Phoenix app" <> _]}
end
end
-
- test "app_name is included in tailwind config" do
- in_tmp_umbrella_project "new with defaults", fn ->
- Mix.Tasks.Phx.New.Web.run(["testweb"])
-
- assert_file "testweb/assets/tailwind.config.js", fn file ->
- assert file =~ "testweb.ex"
- assert file =~ "testweb/**/*.*ex"
- end
- end
- end
end
diff --git a/integration_test/config/config.exs b/integration_test/config/config.exs
index aa9faf5f65..382e357788 100644
--- a/integration_test/config/config.exs
+++ b/integration_test/config/config.exs
@@ -4,6 +4,6 @@ config :phoenix, :json_library, Jason
config :swoosh, api_client: false
-config :tailwind, :version, "3.4.3"
+config :tailwind, :version, "4.0.3"
-config :phoenix_live_view, enable_expensive_runtime_checks: true
\ No newline at end of file
+config :phoenix_live_view, enable_expensive_runtime_checks: true
diff --git a/integration_test/mix.exs b/integration_test/mix.exs
index a63f329ba0..4fc63558b0 100644
--- a/integration_test/mix.exs
+++ b/integration_test/mix.exs
@@ -55,7 +55,7 @@ defmodule Phoenix.Integration.MixProject do
{:bcrypt_elixir, "~> 3.0"},
{:argon2_elixir, "~> 4.0"},
{:pbkdf2_elixir, "~> 2.0"},
- {:tailwind, "~> 0.2"},
+ {:tailwind, "~> 0.3"},
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
diff --git a/priv/templates/phx.gen.live/core_components.ex b/priv/templates/phx.gen.live/core_components.ex
index f5ba025411..2708586066 100644
--- a/priv/templates/phx.gen.live/core_components.ex
+++ b/priv/templates/phx.gen.live/core_components.ex
@@ -520,7 +520,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
width, height, and background color classes.
Icons are extracted from the `deps/heroicons` directory and bundled within
- your compiled app.css by the plugin in your `assets/tailwind.config.js`.
+ your compiled app.css by the plugin in `assets/css/tailwind_heroicons.js`.
## Examples