This is my first time using the new next/font
package. I followed Next.js' tutorial, and it was easy to set up. I'm using both Inter and a custom local typeface called App Takeoff. To actually use both of these typefaces, I'm using Tailwind CSS, where Inter is connected to font-sans
and App Takeoff is connected to font-display
.
I have done plenty of testing between files, and for some reason both typefaces work everywhere except my Modal
component. (See Helpful Update at the bottom for why it doesn't work in the Modal
component.)
index.tsx
modal.tsx via index.tsx
As you can see, the typefaces work just fine when they aren't inside the modal, but as soon as they're in the modal they don't work.
// app.tsx
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter'
})
import localFont from 'next/font/local'
const appTakeoff = localFont({
src: [
{
path: '../fonts/app-takeoff/regular.otf',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.eot',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff2',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.ttf',
weight: '400',
style: 'normal'
}
],
variable: '--font-app-takeoff'
})
const App = ({ Component, pageProps }: AppProps) => {
return (
<div className={`${inter.variable} font-sans ${appTakeoff.variable}`}>
<Component {...pageProps} />
</div>
)
}
export default App
// modal.tsx
import type { FunctionComponent } from 'react'
import type { Modal as ModalProps } from '@/typings/components'
import React, { useState } from 'react'
import { Fragment } from 'react'
import { Transition, Dialog } from '@headlessui/react'
const Modal: FunctionComponent<ModalProps> = ({ trigger, place = 'bottom', className, addClass, children }) => {
const [isOpen, setIsOpen] = useState(false),
openModal = () => setIsOpen(true),
closeModal = () => setIsOpen(false)
const Trigger = () => React.cloneElement(trigger, { onClick: openModal })
const enterFrom = place === 'center'
? '-translate-y-[calc(50%-12rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%-12rem)]'
const mainPosition = place === 'center'
? '-translate-y-1/2'
: 'translate-y-0 sm:-translate-y-1/2'
const leaveTo = place === 'center'
? '-translate-y-[calc(50%+8rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%+8rem)]'
return (
<>
<Trigger />
<Dialog open={isOpen} onClose={closeModal} className='z-50'>
{/* Backdrop */}
<div className='fixed inset-0 bg-zinc-200/50 dark:bg-zinc-900/50 backdrop-blur-sm cursor-pointer' aria-hidden='true' />
<Dialog.Panel
className={`
${className || `
fixed left-1/2
${
place === 'center'
? 'top-1/2 rounded-2xl'
: 'bottom-0 sm:bottom-auto sm:top-1/2 rounded-t-2xl xs:rounded-b-2xl'
}
bg-zinc-50 dark:bg-zinc-900
w-min
-translate-x-1/2
overflow-hidden
px-2 xs:px-6
shadow-3xl shadow-primary-400/10
`}
${addClass || ''}
`}
>
{children}
</Dialog.Panel>
<button
onClick={closeModal}
className='
fixed top-4 right-4
bg-primary-600 hover:bg-primary-400
rounded-full
h-7 w-7 desktop:hover:w-20
overflow-x-hidden
transition-[background-color_width] duration-300 ease-in-out
group/button
'
aria-role='button'
>
Close
</button>
</Dialog>
</>
)
}
export default Modal
I hope this information helps. Let me know if there's anything else that would be helpful to know.
Thank you Jonathan Wieben for explanation of why this isn't working (See Explanation). The issue simply has to do with the scope of the applied styles, and Headless UI's usage of the React Portal
component. If anyone has some ideas of how I can either change where the Portal
is rendered or change the scope of the styles, that would be super helpful. Jonathan Wieben pointed out a way to do this, however—from my testing—it doesn't work with Tailwind CSS.
The Dialog
component you are using renders in a portal (see here).
you typically want to render them as a sibling to the root-most node of your React application. That way you can rely on natural DOM ordering to ensure that their content is rendered on top of your existing application UI.
You can confirm this by inspecting your modal DOM element in your browser and seeing if it is indeed placed outside the div
wrapper from your App
component (I suspect it is).
If so, this is the explanation for why the modal content does not render with the expected font: It is rendered outside the component that defines the font.
To get around this, you could define your font on a higher level, e.g. in your head like described here: Next docs.