devcrea.com
Web & Frontend

Setting Up Vite with Twin Macro: TypeScript, Emotion and Error Fixes

The complete Vite + Twin Macro setup guide covering TypeScript declarations, vite.config.ts configuration, Emotion vs styled-components, and fixes for the most common build errors.

Setting Up Vite with Twin Macro: TypeScript, Emotion and Error Fixes

You set up a fresh Vite React project, install Tailwind, and drop in Twin Macro to handle CSS-in-JS. The dev server runs perfectly, but the moment you hit build or add TypeScript, the terminal floods with macro errors. Configuring Vite with Babel macros requires specific tweaks that official documentation often skips. Here is the exact setup to get Twin Macro running with Emotion and TypeScript without facing those notorious build failures.

  • Core Stack: Vite 5, React, Tailwind CSS v3.4.x
  • Recommended Engine: Emotion
  • Required Plugin: vite-plugin-babel-macros
  • Common Blocker: TypeScript module resolution for the css prop

Emotion vs. Styled-Components: Which One to Choose?

Most developers get stuck deciding between the two main CSS-in-JS engines for Twin Macro. Emotion has slightly better integration with Babel macros and handles server-side rendering without massive configuration overhead. The compilation speed is visibly faster. Go with Emotion unless your legacy codebase explicitly demands Styled-Components.

Step 1: Vite React Project and Dependencies Setup

Start by generating a standard Vite React TypeScript template. Open your terminal and run the scaffolding command.

npm create vite@latest my-app --template react-ts

Navigate into your project folder and install the core styling packages. You need Twin Macro, Tailwind CSS, and the Emotion packages.

npm i twin.macro tailwindcss @emotion/react @emotion/styled

Next, install the development dependencies. These are critical for making Vite understand Babel macros during the build step.

npm i -D vite-plugin-babel-macros babel-plugin-twin

Initialize Tailwind to generate your config file.

npx tailwindcss init -p

Open the generated tailwind.config.js and set the content paths so Tailwind knows which files to scan for class names.

Advertisement
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    './index.html',
    './src/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Step 2: Configuring vite.config.ts and Babel Macros

Vite uses ESBuild under the hood instead of Webpack. This architecture makes it blazing fast, but it means Babel macros will not work out of the box. You must explicitly inject the macro plugin into your Vite configuration.

Update your vite.config.ts file exactly like this:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import macrosPlugin from 'vite-plugin-babel-macros'

export default defineConfig({
  plugins: [
    react(),
    macrosPlugin()
  ],
  define: {
    'process.env': {}
  }
})

Notice the process.env definition at the bottom. Vite environments do not natively support Node variables. Adding this empty object prevents runtime crashes when third-party libraries look for Node environments.

Create a .babelrc file in your project root to handle the auto-injection of imports. This eliminates the need to manually import React or Emotion in every component.

{
  "plugins": [
    "babel-plugin-twin",
    "babel-plugin-macros"
  ]
}

Step 3: Global Styles and TypeScript Declarations

Twin Macro requires global base styles to function. Without them, specific Tailwind utility classes simply will not render.

Create a GlobalStyles.tsx file in your src directory.

import React from 'react'
import { GlobalStyles as BaseStyles } from 'twin.macro'

const GlobalStyles = () => (
  <>
    <BaseStyles />
  </>
)

export default GlobalStyles

Import this component directly into your main.tsx or App.tsx file and place it at the top of your component tree.

Advertisement

TypeScript will still complain about the tw and css props missing from standard HTML elements. You need a dedicated declaration file to extend the React types. Create a twin.d.ts file in your src folder.

import 'twin.macro'
import { css as cssImport } from '@emotion/react'
import styledImport from '@emotion/styled'

declare module 'twin.macro' {
  const styled: typeof styledImport
  const css: typeof cssImport
}

declare module 'react' {
  interface HTMLAttributes<T> extends DOMAttributes<T> {
    tw?: string
    css?: any
  }
}

Keep these declaration merges in a single, well-organized file. Once the project grows past a handful of components, the same discipline shows up when you group shared config and utility logic with static classes in TypeScript instead of scattering loose exports across the codebase.

Handling Common Vite and Twin Macro Errors

Even with a flawless setup, version mismatches or caching issues can trigger specific console errors. Here are the fastest ways to resolve the top three blockers.

Fixing "process is not defined"

This happens right after starting the dev server. Vite runs in the browser context, lacking Node's process object. Always verify that define: { 'process.env': {} } is present inside your vite.config.ts. If the error persists, clear the .vite cache folder inside node_modules.

Resolving MacroError: babel-plugin-macros Not Configured

This error strikes during the production build. The build tool cannot locate your Babel configuration. Ensure your .babelrc file is exactly in the root directory, right next to your package.json file. Moving it inside the src folder breaks the resolution path.

TypeScript ts(2614) Error Fix

Your IDE throws a red underline claiming the module has no exported member css. This is strictly a TypeScript compiler issue. Open your tsconfig.json and ensure you have jsxImportSource set to @emotion/react under compiler options.

{
  "compilerOptions": {
    "jsxImportSource": "@emotion/react"
  }
}

Tailwind CSS v4 Incompatibility

The Tailwind team completely rewrote the core engine for version 4. Twin Macro heavily relies on the internal JavaScript API of Tailwind v3 to transform classes into CSS objects at build time. Upgrading to Tailwind v4 will instantly break your Twin Macro setup.

Advertisement

Always lock your dependency to v3.4.x in your package.json. Do not use the caret symbol for major version bumps here. If you are already working with Tailwind v4 features like CSS-native custom colors, keep that project separate from any Twin Macro setup.

When NOT to Use Twin Macro

Twin Macro is powerful for small to medium projects, but it introduces overhead during the build step because Babel parses every single component. If you are building a massive application with hundreds of complex components, build times will degrade significantly. The engineering team at Railway removed it from their monolithic frontend for this exact reason.

For ultra-large codebases, pure Tailwind CSS or zero-runtime tools like Vanilla Extract is a safer architectural decision. For everything else, the setup above handles the real pain points that trip up most developers on their first attempt. Start with the vite.config.ts change and the twin.d.ts declarations. Those two files resolve about 80% of reported setup issues.

Frequently asked

Questions & answers

Why use Twin Macro instead of just Tailwind CSS?

Twin Macro lets you apply Tailwind classes conditionally based on props or state inside JavaScript, without string concatenation hacks. You get type-safe, composable styling that stays colocated with your component logic. Plain Tailwind requires template literals or third-party libraries like clsx to achieve the same thing.

How do I fix Twin Macro not working with Vite?

Install vite-plugin-babel-macros and add it to your plugins array in vite.config.ts. Also add define: { 'process.env': {} } to the same config. Without both, macros either fail silently during dev or crash on build.

Should I use Emotion or styled-components with Twin Macro?

Emotion is the safer default. It integrates better with Babel macros, compiles faster, and handles SSR without extra configuration. Use styled-components only if your existing codebase already depends on it.

Is Twin Macro still maintained?

Yes. The latest release is v3.4.1 from January 2025, with around 60,000 weekly npm downloads. Active development continues, though Tailwind CSS v4 support is not yet available due to Tailwind's internal API rewrite.

Can I use Twin Macro with Tailwind CSS v4?

Not currently. Twin Macro depends on Tailwind v3's internal JavaScript API to transform class names at build time. Tailwind v4 rewrote this layer entirely, breaking compatibility. Lock your tailwindcss dependency to v3.4.x until official support is announced.

What causes the TypeScript ts(2614) error with Twin Macro?

Missing type declarations. Create a twin.d.ts file in your src folder that augments the twin.macro module and extends React's HTMLAttributes to include the tw and css props. Also add jsxImportSource: @emotion/react to your tsconfig.json compilerOptions.

Share Article