guides
fonts

Custom Font

How to use custom fonts in your project.

Adding custom fonts to your application or website is a typical requirement for projects. Bamboo recommends using custom fonts through CSS variables for consistency.

Setup

Vite setup

Use the Vite integration, then load font packages or your own @font-face declarations from the app's entry module. For example:

src/main.tsx

import 'virtual:bamboo.css'
import './fonts.css'

Put self-hosted font files in public/fonts/ and reference them as /fonts/... in the CSS below. The same imports can live in the root module or layout of a supported Vite framework.

Fontsource

Fontsource (opens in a new tab) streamlines the process of integrating fonts into your web application.

To begin, install your desired font package:

pnpm add @fontsource-variable/fira-code

Next, import the font into your project:

import '@fontsource-variable/fira-code'

Lastly, create a variable to use it as a token in the bamboo config

src/fonts.css

:root {
  --font-fira-code: 'Fira Code Variable', monospace;
}

Vanilla CSS

Declare self-hosted fonts in the stylesheet imported above:

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

Then alias the font names to css variables.

:root {
  --font-mona-sans: 'Mona Sans', sans-serif;
}

Declared this way the family name means nothing to Bamboo, which is why the steps below alias it to a variable and then to a fonts token — the token is what fontFamily ends up accepting. Global Font Face skips that by registering the name for you.

Global Font Face

You can also define global font face in your bamboo config. A family takes one face or an array of them, and each face's src takes one source or an array of them.

This is the option to reach for rather than writing @font-face into global.css. Both emit the same rule, but a family declared here is also registered as a value fontFamily accepts — so css({ fontFamily: 'Fira' }) autocompletes and typechecks without a token. Written as a raw @font-face in global.css, the name is a string Bamboo has never seen, and under strictPropertyValues it is a type error.

export default defineConfig({
  global: {
    fontface: {
      Fira: [
        {
          src: ['url(/fonts/fira.woff2) format("woff2")', 'url(/fonts/fira.woff) format("woff")'],
          fontWeight: 400,
          fontStyle: 'normal',
          fontDisplay: 'swap',
        },
        {
          src: 'url(/fonts/fira-bold.woff2) format("woff2")',
          fontWeight: 700,
          fontStyle: 'normal',
          fontDisplay: 'swap',
        },
      ],
    },
  },
})

Then expose the font names to css variables.

:root {
  --font-fira-code: 'Fira Code Variable', monospace;
}

You can also use global.vars in your bamboo config to define the variables.

export default defineConfig({
  global: {
    vars: {
      '--font-fira-code': 'Fira Code Variable, monospace',
    },
  },
})

Update Bamboo Config

export default defineConfig({
  theme: {
    extend: {
      tokens: {
        fonts: {
          fira: { value: 'var(--font-fira-code), Menlo, monospace' },
          mona: { value: 'var(--font-mona-sans), sans-serif' },
        },
      },
    },
  },
})

Use the custom fonts

import { css } from '../styled-system/css'
 
function Page() {
  return (
    <div>
      <h1 className={css({ fontFamily: 'mona' })}>Mona Sans</h1>
      <code className={css({ fontFamily: 'fira' })}>Fira Code</code>
    </div>
  )
}