Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active September 25, 2024 17:30
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;
}
}
@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!

@skoskie
Copy link

skoskie commented Sep 25, 2024

Note that the output of this mixin actually causes some issues with old versions of IE. I have forked it and fixed those issues, as well as added support for the display property that several comments have mentioned.

https://gist.github.com/skoskie/b0d77b1a559124291a33cc88d3c8f64d

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