Skip to content

Problems with ESM Tailwind config default export and handling of prefix in Typewind #69

@rtatarinov

Description

@rtatarinov

Summary

I’ve encountered two issues when using Typewind with Tailwind CSS:

  1. ESM Tailwind config default export is not handled — when tailwind.config is written in ESM and uses export default, Typewind does not correctly extract the config object.

  2. Tailwind prefix breaks Typewind utilities — if a prefix is set in tailwind.config, the generated Typewind context no longer matches the actual classes in the project.

Below are reproduction steps, expected/actual behavior, and a suggested fix.

1) ESM Tailwind config default export not extracted

Steps to Reproduce

  1. Create a Tailwind config in ESM format:
// tailwind.config.ts
export default {
  content: ["./src/**/*.{ts,tsx,js,jsx,html}"],
  theme: {},
}
  1. Run Typewind’s CLI or code generation.

Actual behavior
Typewind receives the module object instead of the config object itself.
resolveConfig(config) is then called with the wrong value, which causes errors or produces an invalid context.

Expected behavior
If the Tailwind config is ESM with export default, Typewind should use config.default.

Minimal fix
After esbuild preprocessing, adjust the eval result:

- config = import_eval(preprocessedConfig, true);
+ config = import_eval(preprocessedConfig, true).default;

Files affected:

  • node_modules/typewind/dist/cli.js
  • node_modules/typewind/dist/evaluate.js

2) Tailwind prefix breaks utility/type mapping

Steps to Reproduce

  1. Add a prefix to tailwind.config:
export default {
  prefix: "tw-",
  // ...
}

2.Generate or use Typewind utilities in code, e.g.:

tw.bg_red_500

Actual behavior
The generated Typewind context does not account for the prefix, so utility-to-class mapping becomes incorrect.
Classes generated by Tailwind (tw-bg-red-500) no longer match what Typewind produces (bg-red-500).

Expected behavior
Typewind should either:

  • fully support prefix from Tailwind config and generate matching utilities/types, or
  • provide an explicit option to ignore the prefix (documenting the trade-offs).

Temporary workaround
Force the prefix to "" when creating the context:

- return createContext(userConfig);
+ return createContext({ ...userConfig, prefix: "" });

Files affected:

  • node_modules/typewind/dist/cli.js
  • node_modules/typewind/dist/cli.mjs

This restores correct mapping for utilities but ignores the actual Tailwind prefix.

Suggested proper fix

  1. For ESM configs: safely handle both CJS and ESM by extracting:
config = maybeModule?.default ?? maybeModule;
  1. For prefix handling:
  • Pass the actual userConfig.prefix into createContext and ensure the generator uses it for building class names, or
  • Add a Typewind config flag (e.g. ignorePrefix: boolean) so developers can choose whether to honor Tailwind’s prefix.

Patch applied locally (for reference)

diff --git a/node_modules/typewind/dist/cli.js b/node_modules/typewind/dist/cli.js
index fed33f2..8f11fa3 100755
--- a/node_modules/typewind/dist/cli.js
+++ b/node_modules/typewind/dist/cli.js
@@ -92,12 +92,12 @@ function createTypewindContext() {
       platform: "node",
       external: ["node_modules/*"]
     }).outputFiles[0].text;
-    config = (0, import_eval.default)(preprocessedConfig, true);
+    config = (0, import_eval.default)(preprocessedConfig, true).default;
   } else {
     config = require(configFile);
   }
   const userConfig = (0, import_resolveConfig.default)(config);
-  return (0, import_setupContextUtils.createContext)(userConfig);
+  return (0, import_setupContextUtils.createContext)({ ...userConfig, prefix: "" });
 }

 // src/cli.ts
diff --git a/node_modules/typewind/dist/cli.mjs b/node_modules/typewind/dist/cli.mjs
index 8b0ab09..d6fd666 100755
--- a/node_modules/typewind/dist/cli.mjs
+++ b/node_modules/typewind/dist/cli.mjs
@@ -70,7 +70,7 @@ function createTypewindContext() {
     config = __require(configFile);
   }
   const userConfig = resolveConfig(config);
-  return createContext(userConfig);
+  return createContext({ ...userConfig, prefix: "" });
 }

 // src/cli.ts
diff --git a/node_modules/typewind/dist/evaluate.js b/node_modules/typewind/dist/evaluate.js
index 7ad3eb0..afceb39 100644
--- a/node_modules/typewind/dist/evaluate.js
+++ b/node_modules/typewind/dist/evaluate.js
@@ -88,7 +88,7 @@ function createTypewindContext() {
       platform: "node",
       external: ["node_modules/*"]
     }).outputFiles[0].text;
-    config = (0, import_eval.default)(preprocessedConfig, true);
+    config = (0, import_eval.default)(preprocessedConfig, true).default;
   } else {
     config = require(configFile);
   }

@Mokshit06 please take a look at this

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions