Symfony + Tailwind CSS v4: The Optimal Setup for 2026
AI generated
SF
{ }
Symfony · Tailwind CSS v4 · Vite · CSS-first · Frontend
Symfony + Tailwind CSS v4:
The Optimal Setup for 2026

Tailwind CSS v4 brings a fundamental shift: configuration now happens in CSS instead of a JavaScript file. For Symfony projects with Twig templates, that changes the entire setup process and, with Vite as the asset pipeline and the new Oxide engine, it brings noticeably faster build times.

16 min read CSS-first · Vite · JIT · Twig scanning · Production Symfony 7.x · Tailwind CSS v4.x · Vite 6.x · PHP 8.4

1. What has fundamentally changed in Tailwind CSS v4

Tailwind CSS v4 is not an incremental update, it is a complete rebuild. Its core is now written in Rust (the Oxide engine) and delivers build times that, in Symfony projects with many Twig templates, are noticeably shorter than with v3. The most relevant change for Symfony developers is the end of the tailwind.config.js file as the primary configuration source. In Tailwind CSS v4, the entire configuration, colors, spacing, breakpoints, plugins, happens in the CSS file itself, via @theme directives. That means one less JavaScript file in the build process and a clearer separation between the asset pipeline and the styling configuration.

For Symfony projects that previously used Tailwind CSS v3 with Webpack Encore, the migration path is clear: Webpack Encore is replaced by Vite, and the Tailwind CSS v4 plugin for Vite takes over compilation. The Symfony AssetMapper, available since Symfony 6.3, offers an alternative without a build step for JavaScript assets, but for Tailwind CSS v4 a build step is still required, because CSS generation is based on template scanning. The new automatic content detection in Tailwind v4 scans every file in the project directory by default, which in the context of a Symfony project needs to be configured deliberately so it does not search the vendor/ folder.

2. Installation: Vite, Tailwind v4 and Symfony AssetMapper

The recommended stack for Symfony with Tailwind CSS v4 in 2026 is Vite with the @tailwindcss/vite plugin. The plugin integrates directly into Vite's build pipeline and uses the Oxide engine for fast compilation. Installation begins with npm install tailwindcss @tailwindcss/vite vite. The vite.config.js in the Symfony root directory configures the input path to the assets and the output path to the public/build directory that Symfony's asset system knows about.

The Symfony-side asset management via asset() in Twig templates references the files generated by Vite. Vite writes a manifest.json into the output folder that maps the original filenames to the hashed production filenames. pentatrion/vite-bundle reads this manifest and provides a {{ vite_entry_link_tags() }} Twig function that generates the correct CSS link tag, pointing directly to the Vite dev server in development and to the hashed build asset in production. For Symfony projects, this approach is the cleanest integration of Tailwind CSS v4 without the Webpack Encore setup.


// vite.config.js, Symfony + Tailwind CSS v4 setup
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import symfonyPlugin from 'vite-plugin-symfony';

export default defineConfig({
  plugins: [
    tailwindcss(),            // Tailwind v4 Vite plugin, replaces postcss config
    symfonyPlugin(),          // Generates manifest.json for pentatrion/vite-bundle
  ],

  build: {
    // Output to public/build, Symfony serves from there
    outDir: 'public/build',
    rollupOptions: {
      input: {
        // Main CSS entry point, Tailwind v4 @import goes here
        app: 'assets/app.css',
        // Additional JS entry points if needed
        // main: 'assets/app.js',
      },
    },
  },

  server: {
    // Vite dev server port, different from Symfony server port
    port: 5173,
    // Allow requests from Symfony (localhost:8000)
    cors: true,
  },
});

// Installation commands:
// npm install vite tailwindcss @tailwindcss/vite vite-plugin-symfony
// composer require pentatrion/vite-bundle
// bin/console vite:install  (copies entry points to public/)

3. Understanding and using CSS-first configuration

In Tailwind CSS v4, assets/app.css is not only the CSS entry point, it is simultaneously the configuration file. With @import "tailwindcss" you load the entire Tailwind framework. Every configuration that in v3 lived in tailwind.config.js under theme.extend is now defined in the @theme block: color palettes, spacing, font sizes and custom breakpoints. This feels unfamiliar at first for developers who know v3, but it eliminates the back-and-forth between CSS and JavaScript configuration files.

Design tokens in Tailwind v4 are defined as CSS custom properties (variables) and are therefore directly referenceable in any CSS and JavaScript. A token like --color-brand: #2563eb in the @theme block automatically generates all the associated utility classes: text-brand, bg-brand, border-brand. In Symfony Twig templates you use these classes exactly as in v3. The key advantage: once a value is defined as a CSS variable, JavaScript can read and override it at runtime, useful for theme switching without a full CSS rebuild.

4. Getting Twig templates scanned correctly

Tailwind CSS v4 automatically scans every file in the project directory to find utility classes in use and compiles only those into the final CSS. In the context of a Symfony project, that is problematic: the vendor/ folder contains thousands of PHP files and would be scanned unnecessarily. In addition, Symfony generates cache files in the var/ folder that should also be ignored. Configuring content sources in Tailwind v4 is done via @source directives in the CSS file.

The @source directive tells Tailwind CSS v4 which directories and file types to scan. For a Symfony project with Twig templates, you explicitly list templates/**/*.twig, assets/**/*.js and, where applicable, src/**/*.php, if PHP code dynamically assembles Tailwind classes. Classes that are fully assembled dynamically by PHP or JavaScript ('bg-' . $color) are not recognized by any scanner, these must be explicitly registered in a safelist as classes that should always be generated. That is the same behavior as in v3, only the safelist is now defined in the CSS file instead of in tailwind.config.js.


/* assets/app.css, Tailwind CSS v4 entry point for Symfony */

/* Import the full Tailwind framework, replaces @tailwind base/components/utilities */
@import "tailwindcss";

/* Explicit content sources, prevents scanning vendor/ and var/ */
@source "../templates/**/*.twig";
@source "../assets/**/*.js";
/* Only include PHP scanning if classes are dynamically assembled in PHP */
@source "../src/**/*.php";

/* Safelist: always generate these classes (dynamic class names from PHP variables) */
@source unsafe-inline {
  /* Example: color variants that PHP builds dynamically */
  text-red-500 text-green-500 text-blue-500 text-yellow-500
  bg-red-100 bg-green-100 bg-blue-100 bg-yellow-100
}

/* Custom theme configuration, replaces tailwind.config.js theme.extend */
@theme {
  /* Custom brand colors, auto-generates text-brand, bg-brand, border-brand classes */
  --color-brand:       #2563eb;
  --color-brand-dark:  #1d4ed8;
  --color-brand-light: #93c5fd;

  /* Custom fonts */
  --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;

  /* Custom breakpoints, extend the defaults */
  --breakpoint-3xl: 1920px;

  /* Custom spacing */
  --spacing-18: 4.5rem;
  --spacing-22: 5.5rem;
}

5. Design tokens and theme customization in CSS

The @theme system in Tailwind CSS v4 is significantly more powerful than theme.extend in v3. In Symfony projects, it enables the definition of a complete design system directly in the CSS file. Colors, spacing, typography, border radii and shadows are defined as named tokens and are available as CSS variables throughout the entire project. That means a Symfony Twig template can access the same value that produces text-brand via style="color: var(--color-brand)".

For Symfony projects with multiple themes, say a light and a dark theme, or a customer-specific white-label design, theme variants can be defined via @layer theme. Each variant overrides only the relevant CSS variables. The Symfony backend outputs the active theme as a body attribute or CSS class, and the Tailwind v4 CSS variables adapt automatically, without a separate CSS build per theme. This is a significant advantage over v3, where theme switching required either separate CSS builds or complex CSS variable overrides outside of Tailwind.

6. Reusable components with @layer

In Tailwind CSS v4 in Symfony projects, reusable component styles are defined via @layer components. That is the equivalent of v3's plugin system for component classes. A button class .btn-primary bundles Tailwind utilities and can be used in every Twig template without repeating the utility classes. The @apply directive remains available in v4, but is not recommended for new projects in favor of directly using CSS custom properties.

The layering system of Tailwind CSS v4 (base, components, utilities) allows precise control over specificity. Component classes in @layer components can be overridden by utility classes in the Symfony templates, because utilities have higher specificity. That is the fundamental principle that distinguishes Tailwind from classic CSS frameworks: utility classes always win against component classes, so Symfony templates retain full control over the final appearance without producing CSS specificity conflicts.

7. Dark mode in Symfony + Tailwind v4

Dark mode in Tailwind CSS v4 with Symfony Twig templates can be implemented via two strategies. The first is media-query based (prefers-color-scheme): Tailwind automatically generates dark mode variants for every utility class with a dark: prefix, and the browser activates them based on the user's system settings. For Symfony applications that offer a manual theme toggle via a button, the class strategy is better suited: the dark class on the html element activates all dark mode styles.

The Symfony-side implementation of the manual theme toggle stores the user's preference in a cookie or in the Symfony session. When rendering the Twig layout, a ViewModel reads the preference and sets the class attribute on the html tag to dark or leaves it empty. Alpine.js can take over the client-side toggle without a page reload and persist the value in localStorage. The combination of Symfony-side initialization and Alpine.js-side toggling avoids the flash of unstyled content (FOUC) that occurs when the browser initially renders in the wrong theme.

8. Production build and CSS optimization

The production build for a Symfony application with Tailwind CSS v4 and Vite runs in two steps. First, Vite builds all assets: npm run build starts the Vite build, which compiles Tailwind CSS v4, scans every Twig template, includes only the utility classes in use and minifies the CSS. Second, Symfony deploys the assets: bin/console assets:install or, when using the AssetMapper, the corresponding command. The Vite manifest ensures that Symfony templates point to the correct hashed filenames.

The final CSS bundle of a Symfony project with Tailwind CSS v4, given correct @source configuration and normal utility usage, is typically under 30 kB gzip. Compared to v3: Tailwind v4 produces smaller output files through the Oxide engine at equivalent utility usage. CSS custom properties that Tailwind v4 generates for the @theme block do carry a small overhead, but it is negligible compared to the gains from faster build times and better browser integration. The Lighthouse score for a mid-sized Symfony application with Tailwind v4 lands in the same range as v3, the difference is noticeable in the build process, not in load time.


{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "tailwindcss": "^4.0.0",
    "@tailwindcss/vite": "^4.0.0"
  },
  "devDependencies": {
    "vite": "^6.0.0",
    "vite-plugin-symfony": "^7.0.0"
  }
}

/* Deployment sequence for Symfony + Tailwind CSS v4 production:

   1. Build CSS + JS assets:
      npm run build
      → Scans templates/, compiles only used Tailwind classes
      → Outputs to public/build/ with manifest.json

   2. Clear Symfony cache:
      bin/console cache:clear --env=prod

   3. Warm up cache (optional, for performance):
      bin/console cache:warmup --env=prod

   Expected output file size:
   → public/build/assets/app-[hash].css  (typically 10 to 30 kB gzip)
   → public/build/.vite/manifest.json    (maps source names to hashed names)
*/

9. Tailwind v4 vs. v3: what changes for Symfony projects

The switch from Tailwind CSS v3 to v4 is not trivial for Symfony projects, but it is well documented. The most important changes concern the configuration, the build tool and a few utility class names.

Aspect Tailwind CSS v3 Tailwind CSS v4 Symfony relevance
Configuration tailwind.config.js CSS @theme block One less JS file in the project
Build engine PostCSS + Node.js Rust (Oxide) + Vite plugin Noticeably faster build times
Content detection content: [...] in JS config @source in CSS file Twig paths directly in CSS
CSS variables Manual or via plugin Automatic from @theme Theme tokens usable directly in JS
Migration Stable, well known Breaking changes, upgrade tool available Plan for upgrade effort

For new Symfony projects, starting directly with Tailwind CSS v4 is recommended. For existing projects on v3, the official @tailwindcss/upgrade tool offers an automatic migration of tailwind.config.js into @theme blocks and of the content configuration into @source directives. Some utility classes have been renamed in v4, the upgrade guide lists all breaking changes with a side-by-side comparison. In a typical Symfony project, the manual follow-up work after the automatic upgrade takes one to two hours.

Mironsoft

Symfony frontend development, Tailwind CSS v4 and asset pipeline setup

Need a Symfony + Tailwind CSS v4 setup?

We set up Tailwind CSS v4 with Vite, CSS-first configuration and an optimized production build for existing and new Symfony projects, including migration from v3 and dark mode implementation.

Initial setup

Setting up Tailwind v4 + Vite in Symfony, CSS-first configuration and Twig scanning

Migration v3 to v4

Migrating existing Symfony projects from Tailwind v3 to v4, with the upgrade tool and manual follow-up work

Design system

Building @theme tokens, a component library and a dark mode strategy for Symfony

10. Summary

Tailwind CSS v4 in Symfony in 2026 means: configuration in CSS instead of JavaScript, Vite instead of Webpack Encore, @source directives for Twig scanning and @theme blocks for design tokens. The Oxide engine makes the build process significantly faster, and CSS custom properties from the @theme block are directly usable in JavaScript and inline styles. The Vite plugin approach with pentatrion/vite-bundle integrates cleanly into the Symfony asset system with manifest-based filename hashing.

For new Symfony projects, Tailwind CSS v4 with Vite is the clear recommendation. For migration projects, the official upgrade tool is available and automates most of the mechanical work. Dark mode, reusable components and theme customization follow clear patterns in the CSS file. The biggest mental shift is accepting that the app.css file is now both entry point and configuration file, once that concept clicks, Tailwind CSS v4 in Symfony is significantly less complex than v3 with PostCSS configuration and tailwind.config.js.

Symfony + Tailwind CSS v4: the essentials at a glance

CSS-first configuration

@import "tailwindcss" in assets/app.css. No more tailwind.config.js. All tokens in the @theme { } block.

Twig scanning

@source "../templates/**/*.twig" prevents scanning vendor/ and var/. Safelist for dynamically assembled classes.

Vite integration

The @tailwindcss/vite plugin in vite.config.js. pentatrion/vite-bundle for Symfony manifest integration and vite_entry_link_tags().

Production build

npm run build produces hashed CSS in public/build/. Typically 10 to 30 kB gzip with normal utility usage. Followed by a cache clear in Symfony.

11. FAQ: Symfony + Tailwind CSS v4

1Do I still need tailwind.config.js?
No. Configuration now happens in the @theme block in the CSS file. The upgrade tool migrates existing configs automatically.
2Use Webpack Encore with Tailwind v4?
Possible, but not recommended. @tailwindcss/vite is the official path, faster and simpler than Webpack Encore.
3Prevent vendor/ scanning?
Define @source '../templates/**/*.twig' and @source '../assets/**/*.js' in app.css. Limits scanning to the specified paths.
4Define custom colors in v4?
@theme { --color-brand: #2563eb; } automatically generates text-brand, bg-brand, border-brand. The value is also available as a CSS variable.
5Integrate pentatrion/vite-bundle?
composer require pentatrion/vite-bundle, vite-plugin-symfony in vite.config.js. Use vite_entry_link_tags('app') for CSS in the Twig layout.
6Dark mode with Tailwind v4 in Twig?
dark: prefix for media queries. Manual: dark class on the html element. Symfony reads the user preference, Alpine.js switches client-side.
7How large is the final CSS?
With correct @source configuration, typically 10 to 30 kB gzip. The Oxide engine produces equally small or smaller bundles than v3.
8Migration v3 to v4 in Symfony?
npx @tailwindcss/upgrade migrates the config automatically. Manual follow-up work for breaking changes: 1 to 2 hours for a typical Symfony project.
9Still need PostCSS?
No, with the @tailwindcss/vite plugin. PostCSS is only needed for separate plugins like autoprefixer outside of Tailwind.
10What is @layer components for?
Defines reusable component styles. They get overridden by utility classes, because utilities have higher specificity, the fundamental Tailwind principle.