This example uses Blitz.js to build a Next.js App written in TypeScript and styled with Twin + emotion.
Download this example using degit
npx degit https://github.com/ben-rogerson/twin.examples/blitz-emotion-typescript app-name
From within the new folder, run npm install
, then npm run dev
to start the dev server.
Install Blitz.js:
npx blitz new app-name
Install the dependencies
npm install @emotion/react @emotion/styled @emotion/server
npm install -D twin.macro tailwindcss babel-plugin-macros @emotion/babel-preset-css-prop
Install with Yarn
yarn add @emotion/react @emotion/styled @emotion/server
yarn add twin.macro tailwindcss babel-plugin-macros @emotion/babel-preset-css-prop -D
Twin uses the same preflight base styles as Tailwind to smooth over cross-browser inconsistencies.
The GlobalStyles
import adds these base styles, some @keyframes for animations, and some global css variables.
Import GlobalStyles
within a new file placed in app/styles/GlobalStyles.tsx
:
// app/styles/GlobalStyles.tsx
import { Fragment } 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 = () => (
<Fragment>
<BaseStyles />
<Global styles={customStyles} />
</Fragment>
)
export default GlobalStyles
Then import the GlobalStyles file in app/pages/_app.tsx
:
// app/pages/_app.tsx
import {
AppProps,
ErrorBoundary,
ErrorComponent,
ErrorFallbackProps,
useQueryErrorResetBoundary,
} from 'blitz'
+ import { Fragment } from 'react'
+ import GlobalStyles from './../styles/GlobalStyles'
export default function App({ Component, pageProps }: AppProps) {
const getLayout = Component.getLayout || (page => page)
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
onReset={useQueryErrorResetBoundary().reset}
>
{getLayout(
+ <Fragment>
+ <GlobalStyles />
<Component {...pageProps} />
+ </Fragment>,
)}
</ErrorBoundary>
)
}
function RootErrorFallback({ error }: ErrorFallbackProps) {
return (
<ErrorComponent
statusCode={error.statusCode || 400}
title={error.message || error.name}
/>
)
}
Creating a _document.js
file like this will put critical styles in the head of the page.
Without this step, you’ll notice a difference between the SSR generated styles and the ones that hydrate on the client side.
// pages/_document.js
import { Fragment } from 'react'
import {
Document,
Html,
DocumentHead,
Main,
BlitzScript /*DocumentContext*/,
} from 'blitz'
import { extractCritical } from '@emotion/server'
class MyDocument extends Document {
static async getInitialProps(ctx: any) {
const initialProps = await Document.getInitialProps(ctx)
const critical = extractCritical(initialProps.html)
initialProps.html = critical.html
initialProps.styles = (
<Fragment>
{initialProps.styles}
<style
data-emotion-css={critical.ids.join(' ')}
dangerouslySetInnerHTML={{ __html: critical.css }}
/>
</Fragment>
)
return initialProps
}
render() {
return (
<Html lang="en">
<DocumentHead />
<body>
<Main />
<BlitzScript />
</body>
</Html>
)
}
}
export default MyDocument
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: 'emotion',
},
}
b) Or in package.json
:
// package.json
"babelMacros": {
"twin": {
"preset": "emotion"
}
},
Note: The preset gets set to 'emotion' by default, so adding the config is only useful if you want to adjust Twin’s other options.
Add this config to your babel.config.js
:
// babel.config.js
module.exports = {
presets: ['blitz/babel', '@emotion/babel-preset-css-prop'],
plugins: [
'@emotion/babel-plugin',
'babel-plugin-twin', // Optional
'babel-plugin-macros',
],
}
To avoid red squiggly underlines, you’ll need to add the remaining types for your chosen css-in-js framework.
First up, you’ll need to install some types for React:
Then create a file in types/twin.d.ts
and add these declarations:
// types/twin.d.ts
import 'twin.macro'
import styledImport from '@emotion/styled'
import { css as cssImport } from '@emotion/react'
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 css prop
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: CSSInterpolation
}
// The inline svg css prop
interface SVGProps<T> extends SVGProps<SVGSVGElement> {
css?: CSSInterpolation
}
}
Then add the following to your tsconfig.json
:
// tsconfig.json
{
"compilerOptions": {
"jsxImportSource": "@emotion/react"
},
"include": ["types"]
}
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 emotion