18
votes

I'm using https://github.com/styled-components/styled-components.

I'm trying to work out the best strategy for components that require @font-face. I want to make sure each component is independent of its context, so I'm defining font-family styles on each them. But if I use injectGlobal in multiple components, I get multiple @font-face rules for the same font.

Should I just define the @font-face rules in my ThemeProvider entry-point component and live with the fact that the desired font might not be loaded by the browser?

4

4 Answers

29
votes

That's exactly why we made injectGlobal. If you take a look at our docs they say:

We do not encourage the use of this. Use once per app at most, contained in a single file. This is an escape hatch. Only use it for the rare @font-face definition or body styling.

So what I'd do is create a JS file called global-styles.js which contains this code:

// global-styles.js

import { injectGlobal } from 'styled-components';

injectGlobal`
  @font-face {
     font-family: 'My custom family';
     src: url('my-source.ttf');
  }
`

As you can see, all we do here is inject some global styling-in this case @font-face. The last thing necessary to make this work is to import this file in your main entry point:

// index.js

import './global-styles.js'

// More stuff here like ReactDOM.render(...)

This will mean your font-face is only injected once, but still all of your components have access to it with font-family: 'My custom family'!

Doing it this way will give you a flash-of-invisible-text (FOIT), but that has nothing to do with styled-components-it'd be the same if you were using vanilla CSS. To get rid of the FOIT requires a more advanced font loading strategy rather than just @font-faceing.

Since this has nothing to do with styled-components, I highly recommend watching these two Front End Center episodes to learn more about how to best do this: "Crafting Web Font Fallbacks" and "The Fastest Webfont Loader in the West"!

16
votes

And on the other side of the same coin in Chrome:

do not use @font-face inside injectGlobal if using e.g. react-router. You will get re-paint of all of you app on each newly loaded route. And this is why: enter image description here

Same font-files downloaded on each new route. As soon as you include font-face in a separate .css file - problem solves as stated in the last comment in this github issue.

11
votes

injectGlobal is deprecated. Use createGlobalStyle

import { createGlobalStyle } from 'styled-components'

export const GlobalStyle = createGlobalStyle`  
  body {
    font-family: 'Muli', sans-serif;

    h1 {
      font-weight: 800;
      font-size: 36px;

    p {
      font-size: 18px;
    }
  }
`;

Then you can use it in your App.js:

import { GlobalStyle } from './styles/global'

class App extends Component {
  render() {

    return (
      <ThemeProvider theme={theme}>
        <>
          <GlobalStyle/>
          <Container/>
        </>
      </ThemeProvider>

    )
  }
}
1
votes

I agree

I get reloaded with

import { createGlobalStyle } from 'styled-components';
import { silver } from 'shared-components/themes/colors';

export default createGlobalStyle`
    @font-face {
        font-family: "Proxima Nova";
        font-style: normal;
        font-weight: 300;
        font-display: swap;
        src: url("/static/media/fonts/proxima_nova/ProximaNova_300.otf");
    }

and react create app