Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active April 19, 2024 11:51
Show Gist options
  • Save jonathantneal/d0460e5c2d5d7f9bc5e6 to your computer and use it in GitHub Desktop.
Save jonathantneal/d0460e5c2d5d7f9bc5e6 to your computer and use it in GitHub Desktop.
SASS @font-face mixin

Font Face

A mixin for writing @font-face rules in SASS.

Usage

Create a font face rule. Embedded OpenType, WOFF2, WOFF, TrueType, and SVG files are automatically sourced.

@include font-face(Samplino, fonts/Samplino);

Rendered as CSS:

@font-face {
	font-family: "Samplino";
	src: url("fonts/Samplino.eot?") format("eot"),
		 url("fonts/Samplino.woff2") format("woff2"),
		 url("fonts/Samplino.woff") format("woff"),
		 url("fonts/Samplino.ttf") format("truetype"),
		 url("fonts/Samplino.svg#Samplino") format("svg");
}

Create a font face rule that applies to bold and italic text.

@include font-face("Samplina Neue", fonts/SamplinaNeue, bold, italic);

Rendered as CSS:

@font-face {
	font-family: "Samplina Neue";
	font-style: italic;
	font-weight: bold;
	src: url("fonts/SamplinaNeue.eot?") format("eot"),
	     url("fonts/SamplinaNeue.woff2") format("woff2"),
	     url("fonts/SamplinaNeue.woff") format("woff"),
	     url("fonts/SamplinaNeue.ttf") format("truetype"),
	     url("fonts/SamplinaNeue.svg#Samplina_Neue") format("svg");
}

Create a font face rule that only sources a WOFF.

@include font-face(Samplinoff, fonts/Samplinoff, null, null, woff);

Rendered as CSS:

@font-face {
	font-family: "Samplinoff";
	src: url("fonts/Samplinoff.woff") format("woff");
}

Create a font face rule that applies to 500 weight text and sources EOT, WOFF2, and WOFF.

@include font-face(Samplinal, fonts/Samplinal, 500, normal, eot woff2 woff);

Rendered as CSS:

@font-face {
	font-family: "Samplinal";
	font-style: normal;
	font-weight: 500;
	src: url("fonts/Samplinal.eot?") format("eot"),
	     url("fonts/Samplinal.woff2") format("woff2"),
	     url("fonts/Samplinal.woff") format("woff");
}

Notes

IE≥9 prioritizes valid font formats over invalid ones. Therefore, while embedded-opentype is the correct format for an .eot font, eot is used to fool modern IE into prioritizing other, newer font formats.

IE≤8 only supports .eot fonts and parses the src property incorrectly, interpreting everything between the first opening parenthesis ( and the last closing parenthesis ) as a single URL. Therefore, a ? is appended to the .eot’s URL, fooling older IE into reading all other sources as query parameters.

// =============================================================================
// String Replace
// =============================================================================
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
// =============================================================================
// Font Face
// =============================================================================
@mixin font-face($name, $path, $weight: null, $style: null, $exts: eot woff2 woff ttf svg) {
$src: null;
$extmods: (
eot: "?",
svg: "#" + str-replace($name, " ", "_")
);
$formats: (
otf: "opentype",
ttf: "truetype"
);
@each $ext in $exts {
$extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);
$format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);
$src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma);
}
@font-face {
font-family: quote($name);
font-style: $style;
font-weight: $weight;
src: $src;
}
}
@ErikaNovus
Copy link

Hi,

I'm trying to use this mixin on my stylesheet but i've encountered an issue. When choosing a font that is in a higher directory (eg. one folder up) then it encounters an issue at compile.

What I want it to display:

@font-face {
  font-family: "SoftElegance";
  src: url("../fonts/SoftElegance.ttf") format("truetype"); }

The "../" breaks the mixin and doesn't allow it to compile. Anyway to include the ability to use parent directories instead of having to have the fonts folder inside my css folder?

@saschalichtenstein
Copy link

It works for me with @include font-face(Samplino, "../fonts/Samplino");

@DerExilant
Copy link

I do also have a problem with the code. It says:
Invalid CSS after "... $replace: "") ": expected expression (e.g. 1px, bold), was "{")

@anup04sust
Copy link

I do also have a problem with the code. It says
Syntax error: Invalid CSS after " eot": expected ")", was ": "?","
on line 23 of []/scss/mixin/_fontface.scss

@mamsoudi
Copy link

Thank you for the mixin! Works perfectly!

@fabiowitt
Copy link

Nice job!

@sjdeere
Copy link

sjdeere commented Apr 12, 2016

what if there is no woff2
@include font-face("name", "https://cdn.domain/fonts/fontname_LT_4_, 400", normal, eot woff ttf svg);

i look at the output and woff2 is also output

@obcidio
Copy link

obcidio commented Apr 12, 2016

Beautiful.

For anyone else's info, place your paths in quotes and as another variable. Also, remember the path is relative to the final destination of the output css file(s).

@vishalPGit
Copy link

Use @include font-face(Samplino, "../fonts/Samplino") instead of @include font-face(Samplino, ../fonts/Samplino) and its working for me.
Thanks

@surayashivji
Copy link

If we create this as an @function instead of an @mixin to create font variables, is it syntactically correct to say

@return @font-face {
font-family : quote($name); // add quotes to name string
font-style: $style;
font-weight: $weight;
src: $src;
}

also, does the mixin's name, font-face have anything to do with setting the font-face's values at the end of the mixin?

@vovan4
Copy link

vovan4 commented Oct 3, 2016

=font-face($font-family, $file-path, $font-weight: normal, $font-style: normal)
@font-face
font-family: $font-family
src: url('#{$file-path}.eot')
src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'), url('#{$file-path}.woff2') format('woff2'), url('#{$file-path}.woff') format('woff'), url('#{$file-path}.ttf') format('truetype'), url('#{$file-path}.svg##{$font-family}') format('svg')
font-weight: $font-weight
font-style: $font-style

// Chrome for Windows rendering fix: http://www.adtrak.co.uk/blog/font-face-chrome-rendering/
@media screen and (-webkit-min-device-pixel-ratio: 0)
@font-face
font-family: $font-family
src: url('#{$file-path}.svg##{$font-family}') format('svg')

@edkf
Copy link

edkf commented Jan 27, 2017

Helpful. Thanks!

@rahulraguvanshi
Copy link

I used @include font-face("Font-Name", ../fonts/Font-Name, null);
Error: Invalid CSS after "fontname": expected expression

@Titoratus
Copy link

It works fine! Just DO NOT create .sass file, but .scss. Click "Download ZIP", import _mixins.scss into your folder, include it in main .sass file (@import '_mixins.scss' or @import 'mixins.scss' [it's no difference]). Then include your fonts.
For me it's not working — @include font-face(RobotoCondensed, fonts/RobotoCondensed);
But it works — @include font-face(RobotoCondensed, "../fonts/RobotoCondensed/RobotoCondensed", null, null, ttf woff woff2);
Notice that ttf has to be first!

@mattmartini
Copy link

To make this work with asset pipeline in Rails 4.2 I changed url to font-url.

$src: append($src, font-url(quote($path + "." + $extmod)) format(quote($format)), comma);

@elliottmangham
Copy link

Anyone managed to rewrite this in PostCSS?

@edwinarroyolopez
Copy link

Hi, the following worked for me:
(.sass)

@font-face
font-family: 'Roboto'
src: url('./utils/roboto/roboto_regular.woff') format('woff')
body
font-family: Roboto

@nickimola
Copy link

Hi, just as a suggestion (which I've included in the mixin I downloaded):
It would be nice to include font-display: swap so that while the fonts are loading the browser falls back to the defaults.
On mine, I have done it this way:

@font-face { font-family: quote($name); font-style: $style; font-weight: $weight; font-display: swap; src: $src; }

The font-display property is suggested by google here: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization#the_font_display_timeline and you have probably seen it on google dev tool audits as well.

@inzoddex
Copy link

What if you need to use two fonts?

@nickimola
Copy link

What if you need to use two fonts?

This is what i did
@include font-face(One, fonts/one);
@include font-face(Two, fonts/two);

@odyright
Copy link

odyright commented Oct 8, 2019

hello what if we need to use two/multi differents weight for a single font?

@odyright
Copy link

odyright commented Oct 8, 2019

Hi, just as a suggestion (which I've included in the mixin I downloaded):
It would be nice to include font-display: swap so that while the fonts are loading the browser falls back to the defaults.
On mine, I have done it this way:

@font-face { font-family: quote($name); font-style: $style; font-weight: $weight; font-display: swap; src: $src; }

The font-display property is suggested by google here: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization#the_font_display_timeline and you have probably seen it on google dev tool audits as well.

can i have your file?

@nickimola
Copy link

nickimola commented Oct 8, 2019

Hi, just as a suggestion (which I've included in the mixin I downloaded):
It would be nice to include font-display: swap so that while the fonts are loading the browser falls back to the defaults.
On mine, I have done it this way:
@font-face { font-family: quote($name); font-style: $style; font-weight: $weight; font-display: swap; src: $src; }
The font-display property is suggested by google here: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization#the_font_display_timeline and you have probably seen it on google dev tool audits as well.

can i have your file?

Sure


@function str-replace($string, $search, $replace: "") {
	$index: str-index($string, $search);

	@if $index {
		@return str-slice($string, 1, $index - 1) + $replace +
			str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
	}

	@return $string;
}


@mixin font-face($name, $path, $weight: null, $style: null, $exts: eot woff2 woff ttf svg) {
	$src: null;

	$extmods: (
		eot: "?",
		svg: "#" + str-replace($name, " ", "_")
	);

	$formats: (
		otf: "opentype",
		ttf: "truetype"
	);

	@each $ext in $exts {
		$extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);
		$format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);
		$src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma);
	}

	@font-face {
		font-family: quote($name);
		font-style: $style;
		font-weight: $weight;
		font-display: swap;
		src: $src;
	}
}

it's the same, i've just added font-display: swap and nothing more than that.

@odyright
Copy link

odyright commented Oct 8, 2019

😄 thank you

@gusbemacbe
Copy link

Hi, just as a suggestion (which I've included in the mixin I downloaded):
It would be nice to include font-display: swap so that while the fonts are loading the browser falls back to the defaults.
On mine, I have done it this way:
@font-face { font-family: quote($name); font-style: $style; font-weight: $weight; font-display: swap; src: $src; }
The font-display property is suggested by google here: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization#the_font_display_timeline and you have probably seen it on google dev tool audits as well.

can i have your file?

Sure


@function str-replace($string, $search, $replace: "") {
	$index: str-index($string, $search);

	@if $index {
		@return str-slice($string, 1, $index - 1) + $replace +
			str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
	}

	@return $string;
}


@mixin font-face($name, $path, $weight: null, $style: null, $exts: eot woff2 woff ttf svg) {
	$src: null;

	$extmods: (
		eot: "?",
		svg: "#" + str-replace($name, " ", "_")
	);

	$formats: (
		otf: "opentype",
		ttf: "truetype"
	);

	@each $ext in $exts {
		$extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);
		$format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);
		$src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma);
	}

	@font-face {
		font-family: quote($name);
		font-style: $style;
		font-weight: $weight;
		font-display: swap;
		src: $src;
	}
}

it's the same, i've just added font-display: swap and nothing more than that.

@nickimola

Your SCSS does not work. I applied @mixin font-face($name: FontName, $path: fonts/FontName, $weight: null, $style: null, $exts: woff otf) and the CSS got empty.

@singpolyma
Copy link

Would be great if this had a license on it so it could be used in open source projects :)

@SvenBudak
Copy link

It makes maybe sense to add also the local rule: src: local('Roboto Light'), local('Roboto-Light'),

@pglatz
Copy link

pglatz commented Dec 2, 2020

Suppose you have a font family that has versions for normal, bold and italic (like OpenSans, OpenSans-Bold, OpenSans-Italic). Will a single rule like @include font-face("OpenSans", fonts/OpenSans, bold, italic) be smart enough to use the corresponding files. Or would you have to create a separate @font-face for each variant?

@cabb
Copy link

cabb commented Apr 30, 2021

Would be great if this had a license on it so it could be used in open source projects :)

@jonathantneal It would still be great to explicitly define the license of your code.

@richware
Copy link

My css project uses sass. So I have converted the scss to sass and have expanded to include an additional step to allow multiple font faces to be generated at one time. Here is my sass mixins: (don't forget to include the str-replace function)

@use "sass:map"

@mixin font-face-multiple($fonts)
  @each $font in $fonts
    $name: if(map-has-key($font,"name"), map-get($font,"name"), null)
    $path: if(map-has-key($font,"path"), map-get($font,"path"), null)
    $weight: if(map-has-key($font,"weight"), map-get($font,"weight"), null)
    $style: if(map-has-key($font,"style"), map-get($font,"style"), null)
    $display: if(map-has-key($font,"display"), map-get($font,"display"), null)
    $exts: if(map-has-key($font,"exts"), map-get($font,"exts"), null)
    @include font-face($name,$path,$weight,$style,$display,$exts)

@mixin font-face($name, $path, $weight: null, $style: null, $display: null, $exts: (eot woff2 woff ttf svg))
  // extensions: eot woff2 woff ttf svg
  $src: null

  $extmods: (eot:"?",svg:"#"+str-replace($name," ","_"))
  $formats: (otf: "opentype",ttf: "truetype")

  @each $ext in $exts
    $extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext)
    $format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext)
    $src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma,)

  @font-face
    font-family: quote($name)
    font-style: $style
    font-weight: $weight
    @if($display)
      font-display: $display
    src: $src

Then all I have to do is create the mapping and include the mixin font-face-multiple and I have the code I am looking for

Example:

$font-list: ()

$font-list: append($font-list,("name": "Old Town","path": "..fonts/OldTown", "exts": (ttf)))
$font-list: append($font-list,("name": Roboto,"path": "..fonts/Roboto-Regular", "weight":400,"style":normal, "exts": (ttf)))

@include font-face-multiple($font-list)

Result:

@font-face {
  font-family: "Old Town";
  src: url("..fonts/OldTown.ttf") format("truetype");
}
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 400;
  src: url("..fonts/Roboto-Regular.ttf") format("truetype");
}

Disclaimer: The code here does not have any validation and there are some subtle issues needing resolution.

Happy coding!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment