Download this example using degit
npx degit https://github.com/ben-rogerson/twin.examples/next-styled-components folder-nameFrom within the new folder, run npm install, then npm run dev to start the dev server.
Install Next.js
Choose "Yes" for the src/ directory option when prompted.
npx create-next-app@latest --javascriptInstall the dependencies
npm install styled-components
npm install -D twin.macro tailwindcss babel-plugin-macros babel-loaderInstall with Yarn
Choose "Yes" for the src/ directory option when prompted.
yarn create next-appInstall the dependencies
yarn add styled-components
yarn add twin.macro tailwindcss babel-plugin-macros babel-loader --devTwin uses the same preflight base styles as Tailwind to smooth over cross-browser inconsistencies.
The GlobalStyles import adds these base styles along with some @keyframes for the animation classes and some global css that makes the ring classes and box-shadows work.
Due to an issue in styled-components, global styles get added in the wrong order when using styled-components. This gives the tailwind base styles an incorrect specificity.
Until the issue is fixed, the workaround is to export the styles from another file.
You can import GlobalStyles within a new file placed in src/styles/GlobalStyles.js:
// src/styles/GlobalStyles.js
'use client'
import { createGlobalStyle } from 'styled-components'
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro'
const CustomStyles = createGlobalStyle({
body: {
WebkitTapHighlightColor: theme`colors.purple.500`,
...tw`antialiased`,
},
})
const GlobalStyles = () => (
<>
<BaseStyles />
<CustomStyles />
</>
)
export default GlobalStylesThen import the GlobalStyles file in src/app/layout.js:
// src/app/layout.js
import GlobalStyles from '../styles/GlobalStyles'
import StyledComponentsRegistry from '../lib/registry'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<StyledComponentsRegistry>
<GlobalStyles />
{children}
</StyledComponentsRegistry>
</body>
</html>
)
}To avoid the ugly Flash Of Unstyled Content (FOUC), add the following in lib/registry.js:
// src/pages/_document.js
// From https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({ children }) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}Twin’s config can be added in a couple of different files.
a) Either in babel-plugin-macros.config.js:
// babel-plugin-macros.config.js
module.exports = {
twin: {
preset: 'styled-components',
},
}b) Or in package.json:
// package.json
"babelMacros": {
"twin": {
"preset": "styled-components"
}
},Create a new file either in the root or in a config
subfolder:
// withTwin.mjs
import babelPluginMacros from 'babel-plugin-macros'
import babelPluginStyledComponents from 'babel-plugin-styled-components'
import * as path from 'path'
import * as url from 'url'
// import babelPluginTwin from 'babel-plugin-twin'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// The folders containing files importing twin.macro
const includedDirs = [path.resolve(__dirname, 'src')]
/** @returns {import('next').NextConfig} */
export default function withTwin(
/** @type {import('next').NextConfig} */
nextConfig,
) {
return {
...nextConfig,
compiler: {
...nextConfig.compiler,
styledComponents: true,
},
webpack(
/** @type {import('webpack').Configuration} */
config,
options,
) {
config.module = config.module || {}
config.module.rules = config.module.rules || []
config.module.rules.push({
test: /\.(jsx|js)$/,
include: includedDirs,
use: [
{
loader: 'babel-loader',
options: {
sourceMaps: options.dev,
plugins: [
// babelPluginTwin, // Optional
babelPluginMacros,
[babelPluginStyledComponents, { ssr: true, displayName: true }],
],
},
},
],
})
if (typeof nextConfig.webpack === 'function')
return nextConfig.webpack(config, options)
return config
},
}
}Then in your next.config.mjs, import and wrap the main export with withTwin(...):
// next.config.mjs
import withTwin from './withTwin.mjs'
/**
* @type {import('next').NextConfig}
*/
export default withTwin({
reactStrictMode: true,
})Learn how to work with twin
- The prop styling guide - A must-read guide to level up on prop styling
- The styled component guide - A must-read guide on getting productive with styled-components
Learn more about styled-components