DEV Community

Cover image for Next.js 13 Fonts with Tailwind
Sebastian Sdorra
Sebastian Sdorra

Posted on • Originally published at sdorra.dev on

Next.js 13 Fonts with Tailwind

Next.js 13 introduces a new way to use fonts. This new font system will automatically optimize your fonts and creates a fallback font which reduces the CLS (cumulative layout shift) to zero!

The font system allows us to import fonts directly from google fonts:

import { Raleway, Merriweather_Sans } from "@next/font/google";
Enter fullscreen mode Exit fullscreen mode

In this example we use the Raleway and Merriweather Sans font. The font system will download the fonts and ship them with our app, this removes the extra network request to google, which is good for performance and privacy.

After we have imported the fonts we can create an instance and use it in our jsx:

const raleway = Raleway();

<h1 className={raleway.className}>Hello Raleway</h1>
Enter fullscreen mode Exit fullscreen mode

But this is not what we like to do in a tailwind project. In the next section I will show how to use the new font system with tailwind.

Complete example with TailwindCSS

First we have to install the @next/font package.

pnpm add @next/font
# or with yarn
yarn add @next/font
# or with npm
npm install @next/font
Enter fullscreen mode Exit fullscreen mode

In order to import fonts from google we have to specify the subsets of the fonts. We can do this on instance creation of every font or we can specify the subsets in the next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
    fontLoaders: [
      { loader: "@next/font/google", options: { subsets: ["latin"] } },
    ],
  },
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

After that we import the fonts in our root layout and specify a css variable for each font by using the variable attribute. Than we can bind those variables to one of our html elements.

import { FC, PropsWithChildren } from "react";
import { Raleway, Merriweather_Sans } from "@next/font/google";

const raleway = Raleway({
  variable: "--display-font",
});

const merriweather = Merriweather_Sans({
  variable: "--body-font",
});

const RootLayout: FC<PropsWithChildren> = ({ children }) => (
  <html className={`${raleway.variable} ${merriweather.variable}`}>
    <body>
      {children}
    </body>
  </html>
)
Enter fullscreen mode Exit fullscreen mode

Now we can use these variables in our tailwind config to define our font families.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.tsx",
    "./pages/**/*.tsx",
    "./components/**/*.tsx",
  ],
  theme: {
    fontFamily: {
      "display": "var(--display-font)",
      "body": "var(--body-font)",
    },
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

And now we are able to use our fonts with tailwind generated classes:

<h1 className="font-display">Hello from raleway</h1>
<p className="font-body">Hello from Merriweather Sans</p>
Enter fullscreen mode Exit fullscreen mode

Top comments (9)

Collapse
 
rgbskills profile image
Hali

I'm having trouble setting this up for Roboto, I'm getting the following error:

app\layout.js
`@next/font` error:
Missing weight for font `Roboto`.
Available weights: `100`, `300`, `400`, `500`, `700`, `900`
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rgbskills profile image
Hali

I managed to get it working with the following code:

import { Roboto } from "@next/font/google";

const roboto = Roboto({
  variable: "--roboto-font",
  weight: ['100', '300', '400', '500', '700', '900'],
  subsets: ['latin'],
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={roboto.variable}>
      <head />
      <body>
        {children}
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now I can use all the styles:

<p className="font-roboto font-thin">Roboto 100</p>
<p className="font-roboto font-light">Roboto 300</p>
<p className="font-roboto font-normal">Roboto 400</p>
<p className="font-roboto font-medium">Roboto 500</p>
<p className="font-roboto font-bold">Roboto 700</p>
<p className="font-roboto font-black">Roboto 900</p>
<p className="font-roboto font-thin italic">Roboto 100</p>
<p className="font-roboto font-light italic">Roboto 300</p>
<p className="font-roboto font-normal italic">Roboto 400</p>
<p className="font-roboto font-medium italic">Roboto 500</p>
<p className="font-roboto font-bold italic">Roboto 700</p>
<p className="font-roboto font-black italic">Roboto 900</p>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
forguz profile image
Nicolas Dellazzeri

Thanks! This method of implementing variables in the HTML tag works if you want to use the fonts in the global.css too:

layout.tsx

import './globals.css'

import { Inter, Inconsolata, Lora } from 'next/font/google';

export const inter = Inter({
  weight: ['400', '700'],
  display: 'swap',
  variable: '--font-inter',
  fallback: ['Helvetica', 'sans-serif'],
  subsets: ['latin'],
});

export const inconsolata = Inconsolata({
  weight: ['400', '700'],
  display: 'swap',
  variable: '--font-inconsolata',
  fallback: ['Courier New', 'Courier', 'monospace'],
  subsets: ['latin'],
});

export const lora = Lora({
  weight: ['400', '700'],
  display: 'swap',
  variable: '--font-lora',
  fallback: ['Times New Roman', 'Times', 'serif'],
  subsets: ['latin'],
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html className={`${inter.variable} ${inconsolata.variable} ${lora.variable}`} lang="en">
      <body>{children}</body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

globals.css

:root {
  --font-mono: var(--font-inconsolata);
  --font-sans: var(--font-inter);
  --font-serif: var(--font-lora);

  --background-color: #fff;
  --primary-font-color: #2d2d2d;
  --secondary-font-color: #757575;
  --accent-color: #a445ed;

  --max-width: 737px;
}
.
.
.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
shynsky profile image
Marek Wituszynski

I'm not able to make it work - only the first variable passed in the tag works. Rest is ignored. Anyone have the same problem?

Collapse
 
x3daniking profile image
Adnan Khan

same, did you find any fix for it?

Collapse
 
shynsky profile image
Marek Wituszynski

yes, here's in a nutshell:
-app.ts file:

[...]
import { Montserrat, Raleway } from '@next/font/google';

const mont = Montserrat({
  weight: '400',
  subsets:  ['latin', 'latin-ext'],
  variable: '--font-mont',
}); 

const openSans = Raleway({
  weight:   ['400', '700'],
  subsets:  ['latin', 'latin-ext'],
  variable: '--font-opensans',
}); 

const App = ({ Component, pageProps }: AppProps) => (
  <main
    className={${mont.variable} ${openSans.variable}`}>
    <Component {...pageProps} />
  </main>
);
[...]
Enter fullscreen mode Exit fullscreen mode
  • here's tailwind.config.js:
[...]
fontFamily: {
   mont:  'var(--font-mony)',
   openSans:   'var(--font-opensans)',
 },
[...]
Enter fullscreen mode Exit fullscreen mode

Some fonts didn't work without providing optional subset and or weights, so I added them all.

Thread Thread
 
x3daniking profile image
Adnan Khan

Thanks, I’ll look into it in the morning

Collapse
 
mark_meebs_20d6c53a3a6fba profile image
Mark Meebs

Any idea how to get this to work with ladle?

Collapse
 
awaisalwaisy profile image
Alwaisy al-waisy

Thanks, this article became a reason to save my time.