Custom font is not working in Tailwind CSS: fix @font-face
Why DevTools shows your Tailwind font family while the text stays in the default face, and the two-line @font-face change that makes the file load.
TL;DR#
When a custom font "does not work" with Tailwind CSS but DevTools still shows the family on the element, Tailwind is not the problem: the utility class exists and applies. The @font-face rule is what fails. In the Stack Overflow question it fails twice: format('ttf') is not a font format keyword (the keyword for .ttf files is truetype), and font-family: '"Glimer"' declares a family whose name contains the quote characters, so it never matches the Glimer that Tailwind outputs. Declare font-family: 'Glimer', use format('truetype') or drop the hint, and point url() at a path the server actually serves.
The reporter's symptom, in their own words: the font "is still default and showing serif-400. I still can see Glimer in family section but the font seems like not changed."
What the question's CSS asks the browser to do#
The project is React, TypeScript and Next.js with Tailwind CSS v3. The font file sits in a /fonts folder as Glimer-Regular.ttf, and global.css declares it like this:
@layer base {
@font-face {
font-family: '"Glimer"';
src: url(../../fonts/Glimer-Regular.ttf) format('ttf');
}
}The Tailwind configuration extends the theme with a glimer family:
module.exports = {
mode: 'jit',
important: true,
purge: ['./src/pages/**/*.{js,ts,jsx,tsx}'],
darkMode: false,
content: [],
theme: {
extend: {
fontFamily: {
glimer: ['"Glimer"']
},
},
},
plugins: [],
}Three layers are involved, and only one of them is broken. Tailwind turns glimer into a .font-glimer utility. The element gets that class. The browser then has to find an @font-face rule whose family name matches and whose src it is willing to download. That last step is where this setup stops.
Why the font never loads#
format('ttf') is not a format keyword#
The format() hint after url() tells the browser what kind of file it is about to fetch, so it can skip files it cannot use. The MDN reference for the src descriptor lists the valid keywords; the one for TrueType files with the .ttf extension is truetype. There is no ttf keyword. MDN describes what an unsupported hint does:
If the value is not supported or invalid, the browser may not download the resource, potentially saving bandwidth.
The same MDN page also warns that some browsers go further:
Some browsers will ignore the whole descriptor if any item is invalid, even if only one item is invalid.
In the reproduction below, Chromium 155 did exactly that: with format('ttf') the rule did not even appear in document.fonts, and the font file was never requested.
'"Glimer"' puts quotes inside the family name#
In CSS, the outer single quotes delimit a string; everything between them is the value. '"Glimer"' therefore declares a family whose name is eight characters long and begins and ends with a double quote. Tailwind, on the other side, writes font-family: "Glimer", where the double quotes are delimiters and the name is just Glimer. The two names differ, so the browser never selects the @font-face rule, even when its src is valid. The quotes in the Tailwind config are harmless: the build output below shows them passed through as ordinary CSS delimiters. It is the hand-written @font-face that wraps them a second time.
The relative url() has to resolve to a real file#
MDN states that a relative URL in @font-face "is resolved relative to the location of the style sheet containing the @font-face rule". ../../fonts/Glimer-Regular.ttf only works if two levels above the stylesheet there is a fonts folder. The accepted answer avoids the question altogether: it keeps the files in the project's public folder and references them from the root, with url("/font/NewTegomin-Regular.ttf"). The Next.js public folder documentation explains why that path works: files inside public "can then be referenced by your code starting from the base URL (/)". The reproduction below did not test how the Next.js build resolves the original relative path; it served the font from a fixed /fonts/ path in every variant so that only the two descriptors changed.
The same CSS, five ways, in a real browser#
Reproduced on 19 October 2026 in a throwaway node:22-alpine container: Node.js 22.23.3, Tailwind CSS 3.4.19 from npm, and headless Chromium 155 from the Alpine packages. The Tailwind config is the question's, closed with plugins: []; the only file Tailwind scans is a src/pages/index.tsx using className="font-glimer". No Next.js server ran: each variant rebuilt the CSS with the Tailwind CLI, then Chromium loaded a static HTML page that links the compiled CSS, served by a plain Node.js HTTP server, listed document.fonts and measured ten letters "i" at 40px. The font file is DejaVu Sans Mono copied as Glimer-Regular.ttf, a monospace face that makes a loaded font easy to measure against the default.
Tailwind's output was identical for the utility in every variant, and the @font-face rule from @layer base was emitted each time:
557:@font-face {
558- font-family: '"Glimer"';
559-
560- src: url(/fonts/Glimer-Regular.ttf) format('ttf');
563:.font-glimer {
564- font-family: "Glimer" !important;
565-}Variant 1, the question's descriptors. No FontFace entry at all, and the text renders in the fallback face:
computed font-family: Glimer
width of 10 x "i" at 40px: 111pxThe computed family is Glimer. This is the "I still can see Glimer in family section" from the question: the class applies, the face behind it does not exist.
Variant 2, only the format fixed to truetype. The rule is now accepted, but under the quoted name, and nothing uses it:
FontFace family="Glimer" status=unloaded
computed font-family: Glimer
width of 10 x "i" at 40px: 111pxVariant 3, only the name fixed to 'Glimer', format('ttf') kept. Same result as the original: the rule is dropped again.
Variant 4, both fixed. The face loads and the text width changes, because the monospace font is now in use:
FontFace family=Glimer status=loaded
computed font-family: Glimer
width of 10 x "i" at 40px: 241pxVariant 5, plain name and no format() at all, as in the accepted answer. It loads too, with the same width. The test server logged the font file request exactly twice, once for variant 4 and once for variant 5:
GET /4-both-fixed.css
GET /fonts/Glimer-Regular.ttf
GET /5-no-format.html
GET /5-no-format.css
GET /fonts/Glimer-Regular.ttfEither defect alone is enough to keep the default font. Fixing one of them changes nothing visible, which is why this kind of setup tends to survive several rounds of trial and error.
Fix the @font-face rule#
-
Put the font where the server serves it. In a Next.js project that is the
publicfolder at the project root, for examplepublic/fonts/Glimer-Regular.ttf, which the browser can fetch at/fonts/Glimer-Regular.ttf. -
Declare the face with a plain family name and a valid format keyword:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@font-face {
font-family: 'Glimer';
src: url('/fonts/Glimer-Regular.ttf') format('truetype');
}
}If you prefer the accepted answer's form, src: url('/fonts/Glimer-Regular.ttf'); without a hint also loaded in the test. MDN notes that when the hint is omitted, "the browser will download the resource and then detect the format".
- Keep the Tailwind side pointing at the same name, ideally with a fallback stack so the text degrades to something deliberate:
module.exports = {
content: ['./src/pages/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
glimer: ['Glimer', 'sans-serif'],
},
},
},
plugins: [],
}- Use the class on the element and restart the dev server so the new CSS is rebuilt:
export default function Home() {
return <p className="font-glimer">Hello</p>
}The configuration in step 3 also drops the Tailwind v2 keys from the question. They did not cause the font problem, but Tailwind 3.4.19 warns about them on every build:
warn - The `purge`/`content` options have changed in Tailwind CSS v3.0.
warn - Update your configuration file to eliminate this warning.
warn - https://tailwindcss.com/docs/upgrade-guide#configure-content-sources
warn - The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.
warn - Change `darkMode` to `media` or remove it entirely.
warn - https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configurationThey can also mislead you. In the Tailwind v3.4.19 normalizeConfig.js, a purge array is checked before content, so the question's purge globs are the ones actually scanned and the empty content: [] is ignored. Delete purge, mode and darkMode: false, and move the globs into content.
Verify the fix#
Open the page, then run this in the DevTools console:
document.fonts.forEach((f) => console.log(f.family, f.status));A working setup prints Glimer loaded. If the line is missing, the browser rejected the rule: check the format() value and the src syntax. If it prints the name with quotes around it, or unloaded, the declared family does not match what the element asks for. In the Network tab, filter by "Font": the .ttf request should be there with status 200. A 404 means the url() points at the wrong place; no request at all means the rule was dropped or never matched.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When DevTools does not show the family at all#
Everything above assumes the computed font-family already shows your name, which proves Tailwind generated .font-glimer. If the element has the class in the HTML but no font-family rule applies, the utility was never generated: Tailwind did not scan the file that contains the class. That is a different fault, covered in Tailwind CSS not working with Next.js, where the content globs miss the component folders and the build ships without the styles.
Two more traps sit close to this one. In a Pages Router project, importing the global stylesheet that holds @font-face from an ordinary component instead of pages/_app fails the build with Global CSS cannot be imported from files other than your Custom App. And if a fresh npm install pulled Tailwind CSS v4 into a project written for v3, the @tailwind directives and the v3 PostCSS setup break before any font question arises: see the "Missing specifier" and npx tailwindcss init errors, which also covers pinning tailwindcss@3.
On current Next.js versions there is also an alternative to hand-written @font-face: the Next.js fonts guide documents next/font/local, which takes the path of a local font file and generates the font declaration for you.
When a custom font refuses to appear, read the symptom before touching the config. A family name visible in DevTools means Tailwind did its part and the browser rejected or skipped the face; a missing family means the class was never built. In the first case the answer is almost always in two lines of @font-face: a plain family name that matches the config exactly, and either format('truetype') or no hint at all.
Further reading: Fix shadcn combobox: item was not found at the registry.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Tailwind v4 padding and margin not working
Prove that the utility exists but loses the CSS cascade, restore spacing with one layer change, and inspect the child margins behind space-x.
Google Fonts not working in Tailwind + Vite: @import order
Why the font-inter class exists but the text stays in the default face in a React and Vite project, and the one-line move that makes the browser download Inter.
Fix Tailwind v4: dark class changes no background colour
Keep one bg-bgContrast class while changing its inherited colour, with real browser checks for the wrong theme syntax and the inline-token trap.


