Home Blog How to Use Tailwind CSS with Jekyll (2026 Guide)
Tutorial

How to Use Tailwind CSS with Jekyll (2026 Guide)

A step-by-step guide to setting up Tailwind CSS v4 with Jekyll — installation, PostCSS config, purging unused styles, and a working example.

How to Use Tailwind CSS with Jekyll (2026 Guide)

Tailwind CSS and Jekyll are a natural combination — Tailwind’s utility-first approach works beautifully in Liquid templates, and the purging process eliminates unused classes for a tiny final CSS bundle. Here is how to set it up properly in 2026.

Prerequisites

You need Node.js installed alongside Ruby and Jekyll. Check both are available:

node --version   # v18 or higher recommended
ruby --version   # 3.1 or higher
jekyll --version # 4.x

This approach integrates Tailwind into Jekyll’s asset pipeline via PostCSS.

Step 1: Initialise npm in your Jekyll project

cd your-jekyll-site
npm init -y

Step 2: Install Tailwind and PostCSS

npm install -D tailwindcss @tailwindcss/postcss postcss autoprefixer

Step 3: Initialise Tailwind

npx tailwindcss init

This creates tailwind.config.js in your project root.

Step 4: Configure Tailwind content paths

Edit tailwind.config.js to scan your Jekyll template files:

/** @type {import("tailwindcss").Config} */
module.exports = {
  content: [
    "./_includes/**/*.html",
    "./_layouts/**/*.html",
    "./_pages/**/*.html",
    "./_posts/**/*.md",
    "./*.html",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Step 5: Create a PostCSS config

Create postcss.config.js in your project root:

module.exports = {
  plugins: {
    "@tailwindcss/postcss": {},
    autoprefixer: {},
  },
};

Step 6: Create your Tailwind input CSS

Create assets/css/tailwind.css:

@import "tailwindcss";

Step 7: Add a build script to package.json

{
  "scripts": {
    "build:css": "postcss assets/css/tailwind.css -o assets/css/main.css",
    "watch:css": "postcss assets/css/tailwind.css -o assets/css/main.css --watch",
    "dev": "npm run watch:css & bundle exec jekyll serve",
    "build": "npm run build:css && JEKYLL_ENV=production bundle exec jekyll build"
  }
}

In _layouts/default.html, replace your existing CSS link:


<link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">

Step 9: Add compiled CSS to .gitignore (optional)

echo "assets/css/main.css" >> .gitignore

Add it to your git history if you prefer to commit the compiled output for simpler CI/CD.

Step 10: Run the development server

npm run dev

This runs Tailwind in watch mode alongside jekyll serve. Changes to your HTML templates trigger Tailwind to recompile; changes to your Markdown content trigger Jekyll to rebuild.

Method 2: Tailwind CDN (quick start, not for production)

For rapid prototyping, the Tailwind Play CDN works immediately with no build step:

<script src="https://cdn.tailwindcss.com"></script>

Add this to your <head>. This is only for development and prototyping — the CDN loads the full unoptimised Tailwind bundle (~3MB). Never use it in production.

Using Tailwind in Liquid templates

With Tailwind set up, you can use utility classes directly in your layouts and includes:

<!-- _layouts/default.html -->
<nav class="sticky top-0 z-50 bg-white border-b border-gray-100 shadow-sm">
  <div class="max-w-6xl mx-auto px-4 flex items-center justify-between h-16">
    <a href="/" class="text-xl font-bold text-blue-600">JekyllHub</a>
    <ul class="flex gap-6">
      <li><a href="/themes/" class="text-gray-600 hover:text-blue-600 transition-colors">Browse</a></li>
      <li><a href="/blog/" class="text-gray-600 hover:text-blue-600 transition-colors">Blog</a></li>
    </ul>
  </div>
</nav>

<!-- _layouts/post.html -->
<article class="max-w-2xl mx-auto px-4 py-16">
  <h1 class="text-4xl font-bold tracking-tight text-gray-900 mb-4">
    {{ page.title }}
  </h1>
  <div class="prose prose-lg text-gray-700">
    {{ content }}
  </div>
</article>

Adding the Tailwind Typography plugin

The @tailwindcss/typography plugin adds beautiful prose styling for Markdown-rendered content — exactly what you need for blog posts:

npm install -D @tailwindcss/typography

In tailwind.config.js:

module.exports = {
  // ...
  plugins: [
    require("@tailwindcss/typography"),
  ],
};

Then wrap your post content in a prose class:


<div class="prose prose-lg prose-blue max-w-none">
  {{ content }}
</div>

This automatically styles headings, paragraphs, code blocks, blockquotes, lists, and tables in rendered Markdown without any additional CSS.

Dark mode with Tailwind

Enable dark mode in tailwind.config.js:

module.exports = {
  darkMode: "class",
  // ...
};

Then your Jekyll dark mode toggle (which adds a dark class to <html>) works automatically with Tailwind’s dark: variants:

<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  Content
</div>

Production build

For production, run:

npm run build

Tailwind scans all your template files, keeps only the utility classes you actually use, and outputs a tiny CSS file — often under 20kb for a standard site.

Common pitfalls

Dynamic class names not included: Tailwind scans for class names as strings. If you build class names dynamically in Liquid, Tailwind will not detect them:


{# This will NOT work — Tailwind cannot detect dynamic class names #}
<div class="text-{{ page.color }}-600">...</div>

{# Use conditional logic instead #}
{% if page.color == "blue" %}
<div class="text-blue-600">...</div>
{% elsif page.color == "red" %}
<div class="text-red-600">...</div>
{% endif %}

Forgetting to rebuild CSS: When running without the watch script, remember to rebuild CSS after changing templates or adding new Tailwind classes. The dev script handles this automatically.

Tailwind and Jekyll together give you a modern, utility-first CSS workflow with all the simplicity of a static site. Once the setup is in place, building layouts is fast and the output is lean.


Customising the Tailwind theme

Tailwind’s default design system is well-considered, but you will likely want to match your brand colours and typography. Customise in tailwind.config.js:

/** @type {import("tailwindcss").Config} */
module.exports = {
  content: [
    "./_includes/**/*.html",
    "./_layouts/**/*.html",
    "./_pages/**/*.{html,md}",
    "./_posts/**/*.md",
    "./*.html",
  ],
  theme: {
    extend: {
      colors: {
        brand: {
          50:  '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
          900: '#1e3a8a',
        },
      },
      fontFamily: {
        sans: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
        mono: ['Fira Code', 'Consolas', 'monospace'],
      },
      typography: (theme) => ({
        DEFAULT: {
          css: {
            maxWidth: '72ch',
            color: theme('colors.gray.800'),
            a: {
              color: theme('colors.brand.600'),
              '&:hover': { color: theme('colors.brand.700') },
            },
            'h1, h2, h3': {
              color: theme('colors.gray.900'),
              fontWeight: '700',
            },
            code: {
              backgroundColor: theme('colors.gray.100'),
              borderRadius: theme('borderRadius.md'),
              padding: '0.15em 0.4em',
            },
            'code::before': { content: 'none' },
            'code::after': { content: 'none' },
          },
        },
      }),
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
  ],
};

The @tailwindcss/forms plugin resets native form element styles so they can be consistently styled across browsers — essential for <select>, <input>, and <textarea>.


Integrating Tailwind with Jekyll’s Sass

Many Jekyll sites already have Sass-based styles. You can use both Tailwind and Sass together: use Tailwind utilities for layout and components, and Sass for custom components that do not fit well into the utility paradigm.

In your PostCSS input file, you can import both:

/* assets/css/main.css (PostCSS entry point) */
@import "tailwindcss";
@import "./custom.css";    /* Additional non-Tailwind styles */

Or run two separate stylesheets: main.css for Tailwind and custom.css for any legacy Sass. Link both in your layout:


<link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/custom.css' | relative_url }}">

This hybrid approach is common during migrations from a custom CSS system to Tailwind — you can incrementally move components to Tailwind utilities without a big-bang rewrite.


Tailwind CSS v4 specifics

Tailwind v4 (released in 2025) introduced significant changes from v3. Key differences that affect Jekyll setups:

CSS-first configuration. In v4, you configure Tailwind in your CSS file using @theme and @import directives rather than a JavaScript config. The tailwind.config.js approach from v3 is still supported but deprecated. The new approach:

@import "tailwindcss";

@theme {
  --color-brand-500: #3b82f6;
  --color-brand-600: #2563eb;
  --font-family-sans: -apple-system, sans-serif;
}

No PostCSS config required. Tailwind v4 uses its own Vite plugin or standalone CLI, reducing the configuration needed. For Jekyll specifically, the PostCSS approach remains the most practical because Jekyll does not use Vite.

Automatic content detection. In v4, Tailwind automatically scans your project for template files. You may not need the content: configuration section if your file structure follows conventions.

If you are starting a new project, use v4. If you have an existing v3 setup, it continues to work — upgrade on your own schedule.


Building with CI/CD

When deploying with GitHub Actions, the CSS must be compiled before Jekyll builds. Update your workflow:


- name: Set up Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'

- name: Install Node dependencies
  run: npm ci

- name: Build CSS
  run: npm run build:css

- name: Set up Ruby
  uses: ruby/setup-ruby@v1
  with:
    ruby-version: '3.3'
    bundler-cache: true

- name: Build Jekyll
  run: bundle exec jekyll build
  env:
    JEKYLL_ENV: production

The npm ci command installs exact versions from package-lock.json — faster and more deterministic than npm install for CI environments. Cache the node_modules directory between runs with actions/cache if build time matters.


Comparison: Tailwind vs Sass for Jekyll

Both approaches are legitimate. The choice depends on your workflow and team.

Tailwind shines when you want to iterate quickly on layout and spacing directly in HTML without context-switching to a stylesheet. The constraint of only using predefined scale values (spacing 4, 6, 8, 12…) enforces visual consistency automatically. The purging process guarantees tiny CSS output regardless of how many utilities you use.

Sass is better when you have an existing design system with custom tokens, when your team is more comfortable with CSS than utility classes, or when you want complete separation of concerns between structure and style. Sass also handles complex animations and component state (&:hover, &:focus-within, nested selectors) more naturally than Tailwind.

Many Jekyll sites use both: Tailwind for layout utilities (flex, grid, gap, max-w-*, padding and margin) and Sass for complex component logic. This hybrid approach gets the best of both.

Browse Jekyll themes on JekyllHub to see how popular themes approach their CSS architecture — some use Tailwind, many use Sass, and a few use both.


Responsive design with Tailwind in Jekyll

Tailwind’s responsive prefix system works beautifully in Liquid templates. Prefix any utility with a breakpoint (sm:, md:, lg:, xl:) to apply it conditionally:


<!-- Post grid: 1 column mobile, 2 tablet, 3 desktop -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {% for post in site.posts limit: 9 %}
    <article class="bg-white rounded-xl shadow-sm hover:shadow-md transition-shadow overflow-hidden">
      {% if post.image %}
        <img src="{{ post.image | relative_url }}"
             alt="{{ post.title }}"
             class="w-full h-48 object-cover"
             loading="lazy">
      {% endif %}

      <div class="p-5">
        {% if post.category %}
          <span class="text-xs font-semibold uppercase tracking-wide text-blue-600">
            {{ post.category }}
          </span>
        {% endif %}

        <h3 class="mt-2 text-lg font-bold text-gray-900 leading-snug">
          <a href="{{ post.url }}" class="hover:text-blue-600 transition-colors">
            {{ post.title }}
          </a>
        </h3>

        <p class="mt-2 text-sm text-gray-600 line-clamp-2">
          {{ post.description | default: post.excerpt | strip_html }}
        </p>

        <time class="mt-3 block text-xs text-gray-400"
              datetime="{{ post.date | date_to_xmlschema }}">
          {{ post.date | date: "%B %-d, %Y" }}
        </time>
      </div>
    </article>
  {% endfor %}
</div>

The line-clamp-2 utility (from Tailwind’s line-clamp plugin or built into v3.3+) limits the excerpt to two lines with an ellipsis — a neat solution to variable-length post descriptions.


Tailwind’s JIT mode and Jekyll content paths

Tailwind’s Just-in-Time compiler generates only the CSS utilities that appear in your template files. The content paths in tailwind.config.js must cover every file that contains Tailwind class names — if a file is missing from content, its classes will not appear in the compiled CSS.

Common mistake: forgetting _posts/**/*.md. Post content processed through Markdown does not contain Tailwind classes (Markdown produces HTML, not template markup), but if you write raw HTML blocks in your posts, those class names need to be covered:

<!-- Inside a blog post -->
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 my-6">
  **Note:** This is a callout block.
</div>

If .bg-yellow-50 is used only in a Markdown post and not in any template, it still needs to appear in the compiled CSS. Adding "./_posts/**/*.md" to your content paths ensures it does.

For dynamically constructed class names in Liquid (like text-{{ theme.color }}-600), Tailwind cannot detect these at build time. The solution is a safelist:

module.exports = {
  safelist: [
    'text-blue-600', 'text-purple-600', 'text-green-600', 'text-red-600',
    'bg-blue-50', 'bg-purple-50', 'bg-green-50', 'bg-red-50',
  ],
  // ...
};

Or use a pattern:

safelist: [
  { pattern: /text-(blue|purple|green|red)-(600|700)/ },
  { pattern: /bg-(blue|purple|green|red)-50/ },
],

Using Tailwind’s @apply for Jekyll’s prose styles

Tailwind utilities in HTML work well for layout and component styles. But for Jekyll’s rendered Markdown content — where you cannot add Tailwind classes to headings and paragraphs — use @apply in your CSS to apply utilities to element selectors:

/* In your PostCSS input file */
.post-content {
  @apply prose prose-lg prose-blue max-w-none;
}

/* Override specific prose defaults */
.post-content h2 {
  @apply text-2xl font-bold mt-10 mb-4 pb-2 border-b border-gray-100;
}

.post-content blockquote {
  @apply border-l-4 border-blue-500 bg-blue-50 py-2 px-4 rounded-r-lg not-italic;
}

.post-content pre {
  @apply rounded-xl bg-gray-900 text-gray-100 p-4 overflow-x-auto;
}

This approach uses the @tailwindcss/typography plugin’s prose class as the baseline, then overrides specific elements with @apply in your stylesheet. The result is consistent prose styling that matches your Tailwind design system without adding utilities to individual Markdown-rendered elements.

The key advantage over Sass: Tailwind’s spacing scale (mt-10, mb-4) and colour palette (border-blue-500, bg-blue-50) are automatically consistent with your component-level utilities. Change the theme’s primary colour in tailwind.config.js and the prose styles update automatically.


When Tailwind is the wrong choice for Jekyll

Tailwind is not the right CSS approach for every Jekyll project. Understanding when to reach for it and when to use Sass directly saves time.

Tailwind adds build complexity: you need Node.js, npm, a PostCSS config, and a build script. For a simple blog where you are the only author and the CSS needs are modest, this overhead may not be worth it. A well-structured Sass setup with a custom properties design system achieves the same visual consistency with no additional tooling.

Tailwind also produces HTML that is harder to read when class lists grow long. A button with 15 utility classes is harder to scan than .btn .btn-primary. For templates that non-developers will edit — like a team’s marketing site where a designer makes content changes — verbose utility class lists are a readability barrier.

The strong case for Tailwind: design systems that need to stay consistent at scale, teams that are more comfortable in utility-first CSS, projects where arbitrary design decisions should be constrained to a predefined scale, and projects using the Typography plugin where Markdown-rendered content needs careful prose styling without writing CSS selectors for each element type.

The strong case against: solo projects with simple design needs, sites where HTML readability is a priority, teams unfamiliar with the utility-first mental model, and projects where adding a Node.js build step is undesirable or impractical.

Use the right tool for the context. Both approaches produce excellent results in capable hands.

Share LinkedIn