Introduction
Recently, I encountered a project which worked with twin macro. My first thought was that Tailwind is already perfect, so why want more? Then I started to use twin.macro, and I have gotten addicted to it. Not only does it have massive flexibility in styling, but you can also use it with emotion, or styled-components, and it is very clean too, when combined with it’s vs code IntelliSense extension.
So, my goal was to go from having my tailwind looking like this
const SpanStyled = ({ active, children }) => (
<span className={`px-9 py-2 font-bienvenido text-base cursor-pointer rounded-full
${active ? 'bg-brown-600 text-white' : 'bg-transparent text-black'}
hover:bg-brown-200`}>
{children}
</span>
);
to this
const SpanStyled = styled.span(({ active }: { active: boolean }) => [
tw`px-9 py-2 font-bienvenido text-base cursor-pointer rounded-full hocus:(bg-brown-20)`,
active ? tw`bg-brown-600 text-white-900` : tw`bg-transparent text-black-900`
])
I do not need to tell you which would be easier to maintain.
The Setup
Starting with tailwind and dependency installs
Aight, let's jump into it. This is a step-by-step process, which would help you use and be able to set up twin.macro for your vite + react or any other project you bootstrap with vite (I think).
So you have to do the first things first, bootstrap your project as so;
npm create vite@latest
I used the typescript variant. So, you follow the prompt.
Then you install tailwind and its cohorts.
npm install @emotion/styled @emotion/css
npm install -D tailwindcss autoprefixer postcss twin.macro babel-plugin-macros vite-plugin-babel-macros @emotion/babel-plugin @emotion/babel-plugin-jsx-pragmatic @types/babel-plugin-macros @babel/plugin-transform-react-jsx
Aight, when you have installed all these, you would then need to initialise your tailwind with the postcss and autoprefixer, so you do;
npx tailwindcss init -p
Don't forget to add these
@tailwind base;
@tailwind components;
@tailwind utilities;
to your tailwindconfig.json
, inside the content object,
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
Folder and file configs for usage
Okay, we are all done with that. Now to the entire configurations.
Now, if you follow Ben Rogerson’s docs in his explanation, you will see the ability to add the babel field to your package.json
, or to create a babel-plugin-macros.config.js file and add some code to it. I would suggest you rename it to .mjs
, instead of .js
If you are adding to the package.json, you add;
"babelMacros": {
"twin": {
"preset": "emotion"
}
},
Then, if you are creating the babel config, you add this;
module.exports = {
twin: {
preset: 'emotion',
},
}
Alright, well done for coming this distance. All that remains for you to update is your vite.config, add your twin.d.ts, modify your tsconfig.json, and create and import your global styles. It is super easy.
For your vite.config.ts
, or js
for whichever, replace with this;
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import macrosPlugin from 'vite-plugin-babel-macros'
// https://vitejs.dev/config/
export default defineConfig({
optimizeDeps: {
esbuildOptions: {
target: 'es2020',
},
},
esbuild: {
jsxFactory: 'jsx',
jsxInject: 'import { jsx } from "@emotion/react"',
logOverride: { 'this-is-undefined-in-esm': 'silent' },
},
plugins: [
react({
babel: {
plugins: [
'babel-plugin-macros',
[
'@emotion/babel-plugin-jsx-pragmatic',
{
export: 'jsx',
import: '__cssprop',
module: '@emotion/react',
},
],
[
'@babel/plugin-transform-react-jsx',
{ pragma: '__cssprop' },
'twin.macro',
],
],
},
}),
macrosPlugin(),
],
define: {
'process.env': {},
},
})
When you are done with that, you then create a types
folder at the root of your project and create a twin.d.ts
or js
, dependent, and add this
import 'twin.macro'
import { css as cssImport } from '@emotion/react'
import styledImport from '@emotion/styled'
import { CSSInterpolation } from '@emotion/serialize'
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport
const css: typeof cssImport
}
declare module 'react' {
// The tw and css prop
interface DOMAttributes<T> {
tw?: string
css?: CSSInterpolation
}
}
In my case, I also created a .babelrc.js
, and then added this to it _this is not necessary!_
module.exports = {
presets: [
[
"next/babel",
{
"preset-react": {
runtime: "automatic",
importSource: "@emotion/react",
},
},
],
],
plugins: ["@emotion/babel-plugin", "babel-plugin-macros"],
};
The only places we need to modify in our tsconfig.json, are;
"skipLibCheck": true,
"jsxImportSource": "@emotion/react",
and
"include": ["src", "types"],
Here, you would just add “types”, to the already existing src that is there, and you are good to go.
Then, you create a styles folder inside your src folder and create a GlobalStyles.tsx
inside it. Then you would place this code inside it
import React from 'react'
import { Global } from '@emotion/react'
import tw, { css, theme, GlobalStyles as BaseStyles } from 'twin.macro'
const customStyles = css({
body: {
WebkitTapHighlightColor: theme`colors.purple.500`,
...tw`antialiased`,
},
})
const GlobalStyles = () => (
<>
<BaseStyles />
<Global styles={customStyles} />
</>
)
export default GlobalStyles
after you would then import it to your main.tsx, so it affects your application globally. Then, you are good to use twin.macro to start styling your components.
Conclusion.
So, when you are done setting it up, you should be able to edit your code optimally, and use the tw
, instead of the className
. Also note you have to import tw
at the head of each document as you start to work on it.
So, from this
we have this...
If you want access to the file itself, and see how it was done, this is the repository here
https://github.com/Franklivania/vite-twin.gitv
Cheers and happy coding 🍻&💖
Top comments (2)
Hello Chibuzo.
We have reviewed your profile and find it impressive. If you are currently available for work, please reach out to me here. I have an exciting Blockchain project that I believe will capture your interest.
Looking forward to potentially collaborating with you on this project.
Hello Ben,
Is it cool to reply now, as I am just seeing this? It's been a while, I know, but if you are still open to collaborating, I look forward to discussing with you.
Thank you.