Search code examples
tailwind-csstailwind-css-4

Unused classes and variables are included in the native CSS output of Tailwind


Unused CSS classes are included in the output.css when converting Tailwind classes to native CSS.

The error occurs when using Tailwind CLI.

The error occurs when using PostCSS & TailwindCSS together.

Example file:

@import "tailwindcss";
<div class="text-center text-red-500 font-bold text-[2rem] lg:text-[4rem] lg:text-left md:hover:first:text-blue-500 space-y-6">
  Hello, World!
</div>

Result in both cases (2763 lines):

@layer theme {
  :root, :host {
    --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    --color-red-500: oklch(.637 .237 25.331);
    --color-blue-500: oklch(.623 .214 259.815);
    --spacing: .25rem;
    --font-weight-bold: 700;
    --ease-in: cubic-bezier(.4, 0, 1, 1);
    --ease-out: cubic-bezier(0, 0, .2, 1);
    --ease-in-out: cubic-bezier(.4, 0, .2, 1);
    --default-transition-duration: .15s;
    --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
    --default-font-family: var(--font-sans);
    --default-font-feature-settings: var(--font-sans--font-feature-settings);
    --default-font-variation-settings: var(--font-sans--font-variation-settings);
    --default-mono-font-family: var(--font-mono);
    --default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
    --default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
  }
}

@layer base, components;

@layer utilities {
  *, :after, :before, ::backdrop {
    box-sizing: border-box;
    border: 0 solid;
    margin: 0;
    padding: 0;
  }
  
  ::file-selector-button {
    box-sizing: border-box;
    border: 0 solid;
    margin: 0;
    padding: 0;
  }

  /* ... */

  .\@container, .\@container-\[inline-size\] {
    container-type: inline-size;
  }
  
  .pointer-events-auto {
    pointer-events: auto;
  }
  
  /* ... */
  
  .not-sr-only {
    clip: auto;
    white-space: normal;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    position: static;
    overflow: visible;
  }
}

Solution

  • Smooth over cross-browser inconsistencies by Preflight

    Built on top of modern-normalize, Preflight is a set of base styles for Tailwind projects that are designed to smooth over cross-browser inconsistencies and make it easier for you to work within the constraints of your design system.

    The documentation lists all the validated CSS settings that the preflight.css injects into the output.

    This configuration by default adds extra CSS settings to the result. This is not bad, but if it's not needed, you should remove the preflight.css from the multiple lines of code injected by @import "tailwindcss";.

    /* instead of @import "tailwindcss"; */
    
    @layer theme, base, components, utilities;
    @import "tailwindcss/theme.css" layer(theme);
    /* @import "tailwindcss/preflight.css" layer(base); # remove this for disable preflight */
    @import "tailwindcss/utilities.css" layer(utilities);
    

    Automatic Source Detection

    For the TailwindCSS v4 engine, we do not need to specify the sources we use. It automatically finds them. However, it may also search in the node_modules folder, where it can "incorrectly" detect classes that it assumes we are using. This is how unused classes can end up in the output.

    The detection, however, takes into account the rules listed in the .gitignore file and ignores the paths that are blocked there. Therefore, it is always a good idea to create a .gitignore and block node_modules in it:

    /node_modules/
    

    But you can still specify individual sources using the @source directive.

    How to can disable Automatic Source Detection?

    When importing tailwindcss, you have the option to specify the source. You can provide a specific source, or use "none" to exclude everything. Later, you can expand the list of sources with the @source directive.

    @import "tailwindcss" source(none);
    
    @source "../admin";
    @source "../shared";
    

    TailwindCSS v4.0.5

    Use at least version v4.0.5 to ensure that only unused CSS variables are included in the output after the fix in the next PR.

    @theme static directive

    While CSS variables written inside @theme { ... } will only be included in the output if they are used, we have the option to use @theme static { ... }, where variables written inside will always be statically included in the output.

    Extra: Make sure that the file where you declare the @theme variables is also excluded from discovery. I did this in a JS file for PostCSS, and it ended up in the TailwindCSS discovery process, which detected that I was using the CSS variable and kept it in the final result.