Skip to content

Typography in Liquid & CSS

TL;DR: The font object, generating @font-face declarations with font_face, and deriving weights and styles with font_modify.

A font_picker setting (see Font Settings) doesn’t give you a plain string in Liquid. It gives you a font object, with its own properties and its own filters for turning it into working CSS. This page covers that object and the two filters built around it.

Reading a font_picker setting returns a font object:

{
"baseline_ratio": 0.133,
"fallback_families": "sans-serif",
"family": "Assistant",
"style": "normal",
"system?": false,
"variants": {},
"weight": "400"
}
PropertyWhat it holds
familyThe font’s family name. Wrapped in double quotes automatically if it contains non-alphanumeric characters
fallback_familiesThe fallback stack to use alongside family (for example, "sans-serif")
weightThe font’s numeric weight (400, 700, and so on)
stylenormal, italic, or oblique
system?true for a system font, false for a font that needs its own @font-face declaration
variantsAn array of every other font object available in the same family (other weights/styles)
baseline_ratioA decimal used for baseline alignment calculations

Use the font object directly inside a {% style %} tag or a Liquid-processed asset, wherever you need to turn a merchant’s font choice into real CSS:

{% style %}
:root {
--font-family-heading: {{ settings.type_header_font.family }}, {{ settings.type_header_font.fallback_families }};
--font-family-body: {{ settings.type_body_font.family }}, {{ settings.type_body_font.fallback_families }};
}
{% endstyle %}

system? tells you whether a font needs loading at all. System fonts (like Helvetica) are already on the visitor’s device and need no @font-face declaration. Non-system fonts (most of the Google Fonts in Shopify’s library) need one, and the font_face filter generates it for you:

{{ settings.type_header_font | font_face }}
@font-face {
font-family: Assistant;
font-weight: 400;
font-style: normal;
src: url("//your-store.myshopify.com/cdn/fonts/assistant/assistant_n4....woff2") format("woff2"),
url("//your-store.myshopify.com/cdn/fonts/assistant/assistant_n4....woff") format("woff");
}

Pass font_display to control the CSS font-display property, which decides what text looks like while the font is still loading:

{{ settings.type_header_font | font_face: font_display: 'swap' }}

swap shows fallback text immediately and swaps in the real font once it loads, which avoids invisible text during load. See Font Accessibility & Performance for why this matters for perceived performance.

Need a bold or italic variant of a merchant’s chosen font, without a second font_picker setting? font_modify derives it from the base font, the same “derive, don’t duplicate the setting” idea covered for color in Color in Liquid & CSS:

{%- assign bold_font = settings.type_body_font | font_modify: 'weight', 'bold' -%}
h2 {
font-weight: {{ bold_font.weight }};
}
PropertyAcceptsReturns
weight100900, normal, bold, +100/-100 (relative), lighter, bolderThe same family/style, at the requested weight, if that variant exists
stylenormal, italic, obliqueThe same family/weight, in the requested style, if that variant exists

Handle the case where the variant doesn’t exist

Section titled “Handle the case where the variant doesn’t exist”

Not every font family ships every weight and style. If font_modify can’t find the requested variant, it returns nil, not an error:

{%- assign heavy_font = settings.type_body_font | font_modify: 'weight', '900' | default: bold_font -%}

Always pair font_modify with default (falling back to a variant you already know exists, or the base font itself), or check for nil explicitly before using the result. A theme that assumes every font has a 900 weight breaks the moment a merchant picks a font that only ships 400 and 700.

✅ Do❌ Don’t
Check system? before deciding whether a font needs a font_face declaration. Don’t unconditionally generate one for every font.Generating font_face for every font unconditionally, including system fonts that don’t need it and already exist on the visitor’s device.
Pass font_display: 'swap' on font_face calls so fallback text shows immediately instead of invisible text during load.Assuming every font family has a bold and italic variant. font_modify returns nil for a variant that doesn’t exist, and an un-handled nil produces broken CSS.
Always pair font_modify with a default fallback (or an explicit nil check). Never assume a requested weight or style exists.Treating the font object like a plain string. It’s an object with family, weight, style, and other properties, not a CSS-ready value on its own.
Read family and fallback_families together when building a font-family CSS value, don’t drop the fallback stack.
  • font_picker returns a font object: family, fallback_families, weight, style, system?, variants, baseline_ratio.
  • font_face generates the @font-face declaration for non-system fonts; pass font_display: 'swap' to avoid invisible text during load.
  • font_modify derives a different weight or style from the base font. Always pair it with default or a nil check.