A native Pug plugin that allows you to write modern Tailwind CSS classes directly in Pug's elegant dot notation.
By default, Pug's lexer breaks when it encounters special characters like colons :, slashes /, or brackets [] in class names. This forces developers to abandon Pug's terse syntax and fall back to standard HTML class="" attributes. This plugin intercepts and fixes those strings before Pug parses them, restoring a clean authoring experience.
If you are using Eleventy, do not use the official @11ty/eleventy-plugin-pug with this plugin right now.
The official plugin currently uses pug.render, which makes cleanly passing custom preLex plugins difficult and recompiles templates inefficiently. There is an active Pull Request (PR #24) to fix this in the core Eleventy tooling.
Until that PR is merged, you must use a custom Eleventy extension to override the default Pug compiler. See the Eleventy Setup section below for the exact code.
This plugin safely escapes Tailwind's specialized characters so you never have to mix syntax again.
Tailwind uses colons for media queries and pseudo-classes. Pug normally interprets a colon as block expansion.
//- ❌ Before: Forced to use standard attributes
.flex.flex-col(class="md:flex-row hover:bg-blue-500")
//- ✅ After: Clean dot notation
.flex.flex-col.md:flex-row.hover:bg-blue-500
Tailwind uses brackets for on-the-fly values. Pug normally reserves these for attribute blocks.
//- ❌ Before:
.w-full(class="max-w-[850px] bg-[#f8f9fa]")
//- ✅ After:
.w-full.max-w-[850px].bg-[#f8f9fa]
Tailwind uses slashes for fractional widths and opacity modifiers. Pug normally interprets a slash at the end of a tag as a self-closing element.
//- ❌ Before:
.flex(class="w-1/2 bg-black/50")
//- ✅ After:
.flex.w-1/2.bg-black/50
You can stack everything together flawlessly:
//- Perfectly valid with this plugin:
button.w-full.lg:w-[400px].aspect-4/3.hover:bg-white/90.transition-all
npm install pug-plugin-tailwind-syntaxPass the plugin to your standard Pug compiler options:
const pug = require('pug');
const tailwindSyntax = require('pug-plugin-tailwind-syntax');
const html = pug.compileFile('template.pug', {
plugins: [tailwindSyntax]
});Until the official Eleventy Pug plugin is updated, you should handle .pug files natively in your .eleventy.js file. This gives you direct access to the plugins array and actually speeds up your build time by about 10% by caching the compiled template function.
Add this directly to your .eleventy.js configuration:
const pug = require("pug");
const tailwindSyntaxPlugin = require("pug-plugin-tailwind-syntax");
module.exports = function (eleventyConfig) {
// Tell Eleventy to process .pug files
eleventyConfig.addTemplateFormats("pug");
// Write a custom extension that uses pug.compile instead of pug.render
eleventyConfig.addExtension("pug", {
outputFileExtension: "html",
compileOptions: {
permalink: function(contents, inputPath) {
if(typeof contents === "string") {
return function(data) {
return pug.render(contents, { filename: inputPath, ...data });
};
}
return contents;
},
},
compile: async function (inputContent, inputPath) {
// Compile the template exactly once and pass the plugin
const compiled = pug.compile(inputContent, {
filename: inputPath,
basedir: "./views/_includes", // Update this to your includes folder
plugins: [tailwindSyntaxPlugin],
});
// Return the cached function for the render pass
return async function (data) {
return compiled(data);
};
},
});
return {
// ... the rest of your eleventy config
};
};This plugin utilizes Pug's preLex lifecycle hook. It runs a highly optimized regular expression over your raw .pug files immediately after they are read from the disk, but before Pug's lexer attempts to understand them.
When it finds a dot-notation class string containing Tailwind's special characters, it silently transforms it into a standard Pug &attributes block.
For example, .w-full.lg:w-1/2 is invisibly transformed into div&attributes({'class': 'w-full lg:w-1/2'}) right before compilation, preventing the lexer from crashing.