Skip to content

Instantly share code, notes, and snippets.

@marshalhayes
Last active April 14, 2025 05:10
Show Gist options
  • Save marshalhayes/8bfb1fe2ea519727b4d74bfb0a21e672 to your computer and use it in GitHub Desktop.
Save marshalhayes/8bfb1fe2ea519727b4d74bfb0a21e672 to your computer and use it in GitHub Desktop.
Using Tailwind the right way for .NET
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file exposes the following parameters -->
<!-- TailwindVersion: The version of the Tailwind Standalone CLI to download. -->
<!-- TailwindDownloadPath: The path to where to download the Tailwind Standalone CLI. This property is optional, and defaults to %LOCALAPPDATA% on Windows, and $XDG_CACHE_HOME on Linux and MacOS. -->
<!-- TailwindInputStyleSheetPath: The path to the input stylesheet. -->
<!-- TailwindOutputStyleSheetPath: The path to the output stylesheet. -->
<!-- TailwindOptimizeOutputStyleSheet: Whether to optimize the output stylesheet. This property is optional, and defaults to false. -->
<!-- TailwindMinifyOutputStyleSheet: Whether to minify the output stylesheet. This property is optional, and defaults to false when Configuration is Debug, and true when Configuration is Release. -->
<!-- TailwindDownloadUrl: The URL to the Tailwind Standalone CLI. This property is optional, and defaults to downloading the specified version from GitHub. -->
<!-- To override these properties, create a PropertyGroup in the csproj file -->
<!-- For example: -->
<!-- <PropertyGroup> -->
<!-- <TailwindVersion>v4.0.14</TailwindVersion> -->
<!-- <TailwindInputStyleSheetPath>Styles/main.css</TailwindVersion> -->
<!-- <TailwindOutputStyleSheetPath>wwwroot/main.css</TailwindVersion> -->
<!-- </PropertyGroup -->
<PropertyGroup>
<TailwindOptimizeOutputStyleSheet Condition="'$(TailwindOptimizeOutputStyleSheet)' == ''">false</TailwindOptimizeOutputStyleSheet>
<TailwindMinifyOutputStyleSheet Condition="$(TailwindMinifyOutputStyleSheet) == '' And '$(Configuration)' == 'Debug'">false</TailwindMinifyOutputStyleSheet>
<TailwindMinifyOutputStyleSheet Condition="$(TailwindMinifyOutputStyleSheet) == '' And '$(Configuration)' == 'Release'">true</TailwindMinifyOutputStyleSheet>
<!-- The path to where Tailwind should be downloaded to -->
<!-- This should be a path that is writable by the current user, as well as one that is accessible in CI/CD pipelines -->
<!-- By default, this is set to the local app data folder on Windows, and $XDG_CACHE_HOME on Linux and MacOS -->
<!-- On Linux and MacOS, use $XDG_CACHE_HOME or $HOME/.cache -->
<TailwindDownloadPath Condition="'$(TailwindDownloadPath)' == '' And ($([System.OperatingSystem]::IsLinux()) Or $([System.OperatingSystem]::IsMacOS()))">$([MSBuild]::ValueOrDefault($([System.Environment]::GetEnvironmentVariable('XDG_CONFIG_HOME')), $([System.IO.Path]::Combine($([System.Environment]::GetEnvironmentVariable('HOME')), '.cache'))))</TailwindDownloadPath>
<!-- On Windows, use local app data (%LOCALAPPDATA%) -->
<TailwindDownloadPath Condition="'$(TailwindDownloadPath)' == '' And $([System.OperatingSystem]::IsWindows())">$(LOCALAPPDATA)</TailwindDownloadPath>
</PropertyGroup>
<!-- Validate the parameters before download or building -->
<Target Name="ValidateParameters" BeforeTargets="DownloadTailwind; Tailwind">
<!-- Ensure the version is specified -->
<Error Condition="'$(TailwindVersion)' == ''" Text="Tailwind version not specified. Please specify the version. For example: &lt;PropertyGroup&gt;&lt;TailwindVersion&gt;v4.0.14&lt;/TailwindVersion&gt;&lt;/PropertyGroup&gt;"/>
<!-- Ensure the input stylesheet path is specified & the file exists -->
<Error Condition="'$(TailwindInputStyleSheetPath)' == ''" Text="Tailwind input stylesheet not specified. Please specify the path to the input stylesheet in the csproj file. For example: &lt;PropertyGroup&gt;&lt;TailwindInputStyleSheetPath&gt;Styles/main.css&lt;/TailwindInputStyleSheetPath&gt;&lt;/PropertyGroup&gt;"/>
<Error Condition="!Exists('$(TailwindInputStyleSheetPath)')" Text="Tailwind input stylesheet '$(TailwindInputStyleSheetPath)' does not exist. Please specify a path to a stylesheet. For example: &lt;PropertyGroup&gt;&lt;TailwindInputStyleSheetPath&gt;Styles/main.css&lt;/TailwindInputStyleSheetPath&gt;&lt;/PropertyGroup&gt;"/>
<!-- Ensure the output stylesheet path is specified -->
<Error Condition="'$(TailwindOutputStyleSheetPath)' == ''" Text="Tailwind output stylesheet not specified. Please specify the path to the output stylesheet in the csproj file. For example: &lt;PropertyGroup&gt;&lt;TailwindOutputStyleSheetPath&gt;Styles/main.css&lt;/TailwindOutputStyleSheetPath>&lt;/PropertyGroup&gt;"/>
<!-- Ensure the download path is specified -->
<Error Condition="'$(TailwindDownloadPath)' == ''" Text="Tailwind download path not specified. Please specify the download path in the csproj file. For example: &lt;PropertyGroup&gt;&lt;TailwindDownloadPath&gt;/tmp&lt;/TailwindDownloadPath&gt;&lt;/PropertyGroup&gt;"/>
</Target>
<!-- This line supports hot reload by instructing dotnet watch to be aware of modifications to the input stylesheet -->
<ItemGroup Condition="Exists('$(TailwindInputStyleSheetPath)')">
<Watch Include="$(TailwindInputStyleSheetPath)"/>
</ItemGroup>
<Target Name="DownloadTailwind">
<PropertyGroup>
<!-- Determine which version of Tailwind to use based on the current OS & architecture -->
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsLinux()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == X64">tailwindcss-linux-x64</TailwindReleaseName>
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsLinux()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == Armv7">tailwindcss-linux-armv7</TailwindReleaseName>
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsMacOS()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == X64">tailwindcss-macos-x64</TailwindReleaseName>
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsMacOS()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == Arm64">tailwindcss-macos-arm64</TailwindReleaseName>
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsWindows()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == X64">tailwindcss-windows-x64.exe</TailwindReleaseName>
<TailwindReleaseName Condition="$([System.OperatingSystem]::IsWindows()) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == Arm64">tailwindcss-windows-arm64.exe</TailwindReleaseName>
<TailwindDownloadUrl Condition="'$(TailwindDownloadUrl)' == '' And $(TailwindVersion) != 'latest'">https://github.com/tailwindlabs/tailwindcss/releases/download/$(TailwindVersion)/$(TailwindReleaseName)</TailwindDownloadUrl>
<TailwindDownloadUrl Condition="'$(TailwindDownloadUrl)' == '' And $(TailwindVersion) == 'latest'">https://github.com/tailwindlabs/tailwindcss/releases/latest/download/$(TailwindReleaseName)</TailwindDownloadUrl>
</PropertyGroup>
<!-- Download the file -->
<DownloadFile DestinationFolder="$([System.IO.Path]::Combine('$(TailwindDownloadPath)', 'Tailwind', '$(TailwindVersion)'))"
DestinationFileName="$(TailwindReleaseName)"
SourceUrl="$(TailwindDownloadUrl)"
SkipUnchangedFiles="true"
Retries="3">
<Output TaskParameter="DownloadedFile" PropertyName="TailwindCliPath"/>
</DownloadFile>
<!-- On unix systems, make the file executable -->
<Exec Condition="Exists('$(TailwindCliPath)') And ($([System.OperatingSystem]::IsLinux()) Or $([System.OperatingSystem]::IsMacOS()))" Command="chmod +x '$(TailwindCliPath)'"/>
</Target>
<!-- When building the project, run the Tailwind CLI -->
<!-- This target can also be executed manually. For example, with dotnet watch: `dotnet watch msbuild /t:Tailwind` -->
<!-- In order to use hot reload, run both `dotnet watch run` and `dotnet watch msbuild /t:Tailwind` -->
<Target Name="Tailwind" DependsOnTargets="DownloadTailwind" BeforeTargets="Build">
<PropertyGroup>
<!-- Normalize the paths provided -->
<TailwindCliPath>$([MSBuild]::NormalizePath('$(TailwindCliPath)'))</TailwindCliPath>
<TailwindInputStyleSheetPath>$([MSBuild]::NormalizePath('$(TailwindInputStyleSheetPath)'))</TailwindInputStyleSheetPath>
<TailwindOutputStyleSheetPath>$([MSBuild]::NormalizePath('$(TailwindOutputStyleSheetPath)'))</TailwindOutputStyleSheetPath>
<TailwindBuildCommand>"$(TailwindCliPath)" -i "$(TailwindInputStyleSheetPath)" -o "$(TailwindOutputStyleSheetPath)"</TailwindBuildCommand>
<!-- Add optimize flag if specified -->
<TailwindBuildCommand Condition="'$(TailwindOptimizeOutputStyleSheet)' == 'true'">$(TailwindBuildCommand) --optimize</TailwindBuildCommand>
<!-- Add minify flag if specified -->
<TailwindBuildCommand Condition="'$(TailwindMinifyOutputStyleSheet)' == 'true'">$(TailwindBuildCommand) --minify</TailwindBuildCommand>
</PropertyGroup>
<Exec Command="$(TailwindBuildCommand)"/>
</Target>
</Project>
@marshalhayes
Copy link
Author

marshalhayes commented Mar 16, 2025

If you found this useful, leave a 👍 below!

@jonanderson10
Copy link

jonanderson10 commented Mar 16, 2025

👍 thanks!

@spicyramen26
Copy link

Im the guy with the SpecialFolder problem on msbuild. This is how it currently looks in my code:
$([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData))

Maybe you can try building it on your system using windows and not WSL? I'm not really sure what the problem is.

@marshalhayes
Copy link
Author

marshalhayes commented Mar 17, 2025

Absolutely!

This should work for you on Windows:

<!-- Change the download path to this  -->
<TailwindDownloadPath Condition="'$(TailwindDownloadPath)' == '' And $([System.OperatingSystem]::IsWindows())">$(LOCALAPPDATA)</TailwindDownloadPath>

<!-- Remove the quotes from the build command -->
<TailwindBuildCommand>$(TailwindCliPath) -i $(TailwindInputStyleSheetPath) -o $(TailwindOutputStyleSheetPath) --cwd $(ProjectDir)</TailwindBuildCommand>

Thank you for catching this!

Removing the quotes on the build command is slightly worrying. I'll need to look for a more permanent solution there. Until I find a permanent solution, you won't be able to use a path that includes a space anywhere.

@spicyramen26
Copy link

I'm still getting the same error unfortunately, I also tried building your mudblazor-tailwind template and I get the same error.

@marshalhayes
Copy link
Author

Just to make sure I understand, you changed the TailwindDownloadPath here to the below?

<TailwindDownloadPath Condition="'$(TailwindDownloadPath)' == '' And $([System.OperatingSystem]::IsWindows())">$(LOCALAPPDATA)</TailwindDownloadPath>

And similarly, the TailwindBuildCommand here:

<TailwindBuildCommand>$(TailwindCliPath) -i $(TailwindInputStyleSheetPath) -o $(TailwindOutputStyleSheetPath) --cwd $(ProjectDir)</TailwindBuildCommand>

@spicyramen26
Copy link

Yes I copy pasted your code and changed both, making sure that I changed the TailwindDownloadPath for windows and not linux/mac. I made sure to check that msbuild works on my mvc projects and it does.

@marshalhayes
Copy link
Author

I've provided a revised version here. I tried it myself on Windows & it works.

Let me know it if works for you as well!

@kallebysantos
Copy link

kallebysantos commented Mar 18, 2025

Hi @marshalhayes you did a great job here!
Please have a look in my tailwind-dotnet repo, I'd cover all the MsBuild part as well Hot Reload support, it works completely out-of-box, Pls feel free to provide any suggestions. Maybe we could combine our ideas in an Unified Tailwind solution

@marshalhayes
Copy link
Author

For sure! Let me know your thoughts on the framework guide.

@kallebysantos
Copy link

Hi @marshalhayes, since I already built an out-of-box integration I suggest to we use mine as default.
Please have a look on it and you'll see, I would like to read your ideas and I'm able to any PR you may have to increment it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment