Skip to content
MCG Filament CMS Demo

Fonts

This document explains how a site built on Filament CMS loads its typefaces, and how to give a skin its own fonts: from Bunny Fonts, from font files the site hosts itself, or from both at once. It is written for the developer who builds or changes a skin. The skin system itself is described in Design system.

How the CMS loads fonts

A font reaches a page in two separate steps, and a skin needs both:

  1. Loading. The browser downloads the font files. The CMS can do this for the site through Bunny Fonts, or the site can serve the files itself.
  2. Using. The skin's stylesheet names the font in a font token. A font that is loaded but not named in a token is downloaded and never shown, and a font that is named but not loaded is replaced by the next font in the token's list.

The font tokens

Every font on a page comes from one of four CSS custom properties, defined in resources/css/cms.css:

Token Where it is used Base value
--font-cms-display Headings and large display text 'Instrument Sans', ui-sans-serif, system-ui, sans-serif
--font-cms-body Interface text, buttons, forms and short copy 'Instrument Sans', ui-sans-serif, system-ui, sans-serif
--font-cms-prose Long-form rich text, such as article bodies 'Newsreader', ui-serif, Georgia, serif
--font-cms-mono Code ui-monospace, 'SF Mono', Menlo, monospace

A skin changes a font by setting these tokens inside its own selector, for example [data-cms-skin='mcg']. The values after the first name are fallbacks: the browser uses the first font in the list that it has loaded.

What the CMS requests from Bunny Fonts

When theme.fonts in config/cms.php is true, which is the default, the page head (resources/views/components/head.blade.php) contains a <link rel="preconnect" href="https://fonts.bunny.net"> element and one stylesheet link to https://fonts.bunny.net/css?family=…&display=swap. Theme::fontFamilies() (src/Support/Theme.php) decides which families that link requests, in this order:

  1. The families the active skin declares, in theme.skin_fonts in config/cms.php or in the third argument of SkinRegistry::register().
  2. When the skin declares none and it is one of the six skins the package ships, the families listed for it in the table below.
  3. When neither applies, the base pair: Instrument Sans and Newsreader.

Only one of the three is requested. A skin that declares its own families does not also receive the base pair.

Skin Families requested
Base (no skin) instrument-sans:400,500,600,700, newsreader:400,400i,500,600
press fraunces:400,500,600,700, newsreader:400,400i,500,600
signal bricolage-grotesque:400,600,700,800, archivo:400,500,600, jetbrains-mono:400,500
marquee archivo:400,600,700,800,900, public-sans:400,500,600,700, ibm-plex-mono:400,500
saas inter:400,500,600,700,800, jetbrains-mono:400,500
arcade press-start-2p:400, pixelify-sans:400,500,600,700, vt323:400
market montserrat:400,500,600,700

display=swap makes the browser show the text in a fallback font while the font file downloads, so text is never hidden while a font loads.

When theme.fonts is false, the CMS adds neither link, and the site is responsible for loading every font it uses.

A registered skin receives the base pair

A skin that the site registers, in theme.skins or with SkinRegistry::register(), is not one of the six in the table. Unless it declares its own families, the CMS requests Instrument Sans and Newsreader for it, whatever its stylesheet sets the font tokens to. The page then shows the fallback fonts instead of the skin's, and nothing reports an error. This is the most common reason a new skin's fonts do not appear.

Choosing a method

The skin's fonts Method Section
All available on Bunny Fonts List them in theme.skinfonts Adding Bunny Fonts to a skin
All served from the site's own files Turn the CMS's font loading off and declare the files with @font-face Self-hosting the fonts
Some from Bunny Fonts and some self-hosted List only the Bunny families in theme.skinfonts, and declare the others with @font-face Using Bunny Fonts and self-hosted fonts together

Self-hosting is the choice when a font is not on Bunny Fonts (a licensed brand font, for example), or when the site must not make requests to another host.

Adding Bunny Fonts to a skin

This example gives a skin named mcg Montserrat for headings and Lora for long-form text.

  1. At https://fonts.bunny.net, search for each family and note two things: its identifier, which is the family name in lower case with hyphens for spaces (montserrat, lora, source-sans-3), and the weights the skin's stylesheet uses. Request only those weights: each weight is a separate file that every visitor downloads. Add i to a weight for its italic, for example 400i.
  2. In config/cms.php, under theme, list the families under skin_fonts, keyed by the skin's name exactly as it is registered:

PHP

'theme' => [
    // ...
    'skin_fonts' => [
        'mcg' => ['montserrat:600,700', 'lora:400,400i,700'],
    ],
],

A skin registered from a service provider passes the same list as the third argument instead:

PHP

app(SkinRegistry::class)->register('mcg', 'MCG Strategic', ['montserrat:600,700', 'lora:400,400i,700']);
  1. In the skin's stylesheet, for example resources/css/skins/mcg.css, name the fonts in the tokens. Use the family's display name, with its capitals and spaces, in quotes:

CSS

[data-cms-skin='mcg'] {
    --font-cms-display: 'Montserrat', ui-sans-serif, system-ui, sans-serif;
    --font-cms-prose: 'Lora', ui-serif, Georgia, serif;
}

A token the skin does not set keeps its base value. In this example --font-cms-body is still Instrument Sans, which the CMS no longer requests, because the skin now declares its own families. Set every token the skin's design uses, or add the base family to skin_fonts.

  1. In a terminal at the site's root, clear the cached configuration and rebuild the CSS:

Shell

php artisan config:clear
npm run build
  1. In the browser, follow Checking that a font loads.

Self-hosting the fonts

This example serves Source Sans 3 from the site's own files.

  1. In resources/fonts/ in the site, add the font files in WOFF2 format, one file per weight and style, for example source-sans-3-400.woff2 and source-sans-3-700.woff2. Check that the font's license allows hosting it on a website.
  2. In the skin's stylesheet, for example resources/css/skins/mcg.css, declare each file with @font-face, above the skin's selector. The path is relative to the stylesheet, and Vite copies each file into public/build when the CSS is built:

CSS

@font-face {
    font-family: 'Source Sans 3';
    src: url('../fonts/source-sans-3-400.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Source Sans 3';
    src: url('../fonts/source-sans-3-700.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}
  1. In the same stylesheet, name the family in the tokens, spelled exactly as in font-family above:

CSS

[data-cms-skin='mcg'] {
    --font-cms-display: 'Source Sans 3', ui-sans-serif, system-ui, sans-serif;
    --font-cms-body: 'Source Sans 3', ui-sans-serif, system-ui, sans-serif;
}
  1. In config/cms.php, under theme, set 'fonts' => false. Without this, the CMS still requests Instrument Sans and Newsreader from Bunny Fonts on every page, and visitors download two families nothing displays. false turns the CMS's font loading off for every skin, so a site that switches between several skins must self-host the fonts of all of them.
  2. In a terminal at the site's root, run php artisan config:clear, then npm run build.
  3. In the browser, follow Checking that a font loads.

Using Bunny Fonts and self-hosted fonts together

A skin can take some fonts from Bunny Fonts and serve others itself. This example keeps a self-hosted Source Sans 3 for body text and adds Montserrat from Bunny Fonts for headings.

  1. In config/cms.php, under theme, keep 'fonts' => true, and list only the families that come from Bunny Fonts:

PHP

'skin_fonts' => [
    'mcg' => ['montserrat:600,700'],
],

Listing at least one family for the skin also stops the CMS from requesting the base pair for it.

  1. In the skin's stylesheet, declare the self-hosted files with @font-face as in Self-hosting the fonts, and name both families in the tokens:

CSS

[data-cms-skin='mcg'] {
    --font-cms-display: 'Montserrat', ui-sans-serif, system-ui, sans-serif;
    --font-cms-body: 'Source Sans 3', ui-sans-serif, system-ui, sans-serif;
}
  1. In a terminal at the site's root, run php artisan config:clear, then npm run build.
  2. In the browser, follow Checking that a font loads. Montserrat loads from fonts.bunny.net, and Source Sans 3 loads from the site's own /build/assets/ path.

Checking that a font loads

  1. In the browser, open a page that uses the skin, open DevTools with F12, select the Network tab, then the Font filter, and reload the page. Each family the skin uses appears as one or more .woff2 requests, from fonts.bunny.net or from /build/assets/, each with status 200.
  2. In DevTools, in the Elements tab, select a heading, open the Computed pane, and scroll to Rendered Fonts at the bottom. It names the font the browser actually drew the text with. A fallback name there, such as Arial or Segoe UI, means the font was named but not loaded.
  3. In an MCP client connected to the site, call cms_theme_state. Its fonts entry reports whether the CMS loads fonts (loading), the Bunny Fonts URL it requests (url), the families in that URL (families), where they came from (families_source: skin, package or base), and whether the active skin is served the base pair (serving_base_families). It also returns the finding fonts_fall_back_silently when the active skin was registered by the site and receives the base pair.

Problems and their causes

What is seen Cause Fix
The skin's headings show Instrument Sans The skin was registered by the site and declares no families, so the CMS requested the base pair Add the skin's families to theme.skinfonts
The text shows a fallback font, and Network has no request for the font The family is named in a token but loaded by nothing Add it to theme.skinfonts, or declare it with @font-face
skinfonts was changed and nothing changed on the site The configuration is cached Run php artisan config:clear
Network shows a Bunny Fonts request, and the text still shows a fallback The name in the token differs from the family's display name, for example 'Source Sans 3' against 'Source Sans Pro' Copy the display name from fonts.bunny.net, or from the font-family in @font-face
Bold text looks smeared or wider than in the design The weight is not requested, so the browser draws a synthetic bold from a lighter weight Add the weight, for example montserrat:400,700
A self-hosted font returns 404 The url() path in @font-face does not lead from the stylesheet to the file, or the CSS was not rebuilt Correct the relative path and run npm run build
No font loads, and the browser console reports a Content Security Policy error The site sends a Content-Security-Policy header that does not allow fonts.bunny.net Allow https://fonts.bunny.net in style-src and font-src