tailwindlabs/tailwindcss · error · Error

The path given to `source(…)` must be a directory but got `s

Error message

The path given to `source(…)` must be a directory but got `source(${basePath})` instead.

What it means

The Vite plugin validates each `source(…)` path resolves to a directory. It stats `basePath` and, if the stat either fails or the entry is not a directory, throws. Unlike the Turbopack/Webpack variants, the Vite version treats both not-found and not-a-directory as failure (`isDir` is false on rejection).

Source

Thrown at packages/@tailwindcss-vite/src/index.ts:667

        }
        // Ensure relative is a posix style path since we will merge it with the
        // glob.
        relative = normalizePath(relative)

        addWatchFile(path.posix.join(relative, glob.pattern))

        let root = this.compiler.root

        if (root !== 'none' && root !== null) {
          let basePath = normalizePath(path.resolve(root.base, root.pattern))

          let isDir = await fs.stat(basePath).then(
            (stats) => stats.isDirectory(),
            () => false,
          )

          if (!isDir) {
            throw new Error(
              `The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`,
            )
          }
        }
      }
      DEBUG && I.end('Register dependency messages')
    }

    DEBUG && I.start('Build CSS')
    let code = this.compiler.build([...this.candidates])
    DEBUG && I.end('Build CSS')

    DEBUG && I.start('Build Source Map')
    let map = this.enableSourceMaps ? toSourceMap(this.compiler.buildSourceMap()).raw : undefined
    DEBUG && I.end('Build Source Map')

    return {
      code,

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Point `source(…)` at a directory or use a glob whose literal prefix is a directory.
  2. Ensure the directory exists at build/dev start time (create it or order your scripts so generators run first).
  3. Avoid pointing `@source` at individual files.

Example fix

/* before */
@source "./src/main.tsx";

/* after */
@source "./src/**/*.{ts,tsx}";
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from 'node:fs/promises'
async function assertSourceDir(pattern: string, base: string) {
  const p = path.resolve(base, pattern)
  const isDir = await stat(p).then(s => s.isDirectory()).catch(() => false)
  if (!isDir) throw new Error(`source(${pattern}) must resolve to an existing directory: ${p}`)
}

Try / catch

try {
  await build()
} catch (e) {
  if (/source\(.*\) must be a directory/.test((e as Error).message)) {
    // point source() at a directory or fix the glob
  }
  throw e
}

Prevention

When it happens

Trigger: `source("./src/index.html")` pointing at a file, or `source("./does-not-exist")`. `fs.stat(basePath)` rejects or returns a non-directory, `isDir` is false, and index.ts:667 throws.

Common situations: Pointing `@source` at a file instead of a directory/glob, a source directory deleted during dev, or a path that is only present after a generate step that hasn't run.

Related errors


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/69d928313a0a2c5b. Report an issue: GitHub.