Skip to content

Color in Liquid & CSS

TL;DR: Deriving shades, tints, and formats from a base color at render time with Liquid’s color filters, instead of hardcoding them.

Once a color exists as a token (see Color Design Tokens), you rarely need just that one value. A button needs a hover state a shade darker. A badge needs a translucent background. A design spec might hand you an OKLCH value when your CSS needs hex. This page covers Liquid’s built-in color filters, which derive all of these from one stored value instead of you hardcoding every variant by hand.

Why derive colors instead of hardcoding variants

Section titled “Why derive colors instead of hardcoding variants”

Say a merchant sets their primary brand color to #1a5f4f using a color setting or a color_palette entry (see Color Palettes). Your theme still needs a hover state, maybe a shade darker, and perhaps a light tint for a badge background.

Two ways to get there:

{% comment %} ❌ WRONG — a second setting the merchant now has to keep in
sync with the first by hand every time they change their brand color {% endcomment %}
{{ settings.color_primary }}
{{ settings.color_primary_hover }}
{% comment %} ✅ RIGHT — one stored value, the hover shade is derived
at render time and always stays in sync automatically {% endcomment %}
{{ settings.color_primary }}
{{ settings.color_primary | color_darken: 10 }}

Adding a second setting for every derived shade means a merchant has to update two, three, or more settings in lockstep every time they change one brand color, and nothing stops them from getting it wrong. Deriving the shade with a filter means there’s exactly one source of truth. It’s mathematically impossible for the hover color to drift out of sync with the base color.

FilterWhat it doesExample
color_darkenDarkens by a percentage{{ color | color_darken: 10 }}
color_lightenLightens by a percentage{{ color | color_lighten: 20 }}
color_mixBlends two colors by a ratio{{ color_a | color_mix: color_b, 50 }}
color_modifyChanges one channel (e.g. alpha){{ color | color_modify: 'alpha', 0.5 }}
color_saturateIncreases saturation{{ color | color_saturate: 15 }}
color_brightnessReturns a 0–255 brightness value (for contrast checks){{ color | color_brightness }}
color_to_hexConverts to hex format{{ color | color_to_hex }}
color_to_hslConverts to HSL format{{ color | color_to_hsl }}
color_to_rgbConverts to RGB format{{ color | color_to_rgb }}
color_to_oklchConverts to OKLCH format{{ color | color_to_oklch }}

Worked example: deriving a hover state and a translucent badge

Section titled “Worked example: deriving a hover state and a translucent badge”
{% comment %} theme.liquid or a snippet that builds root CSS variables {% endcomment %}
<style>
:root {
--color-primary: {{ settings.color_primary }};
--color-primary-hover: {{ settings.color_primary | color_darken: 10 }};
--color-primary-tint: {{ settings.color_primary | color_modify: 'alpha', 0.1 }};
}
</style>
.button--primary { background: var(--color-primary); }
.button--primary:hover { background: var(--color-primary-hover); }
.badge--primary { background: var(--color-primary-tint); }

The merchant sets one color. Everything else, the hover shade and the translucent tint, is computed from it at render time. Change the base color in the theme editor, and every derived value updates automatically, with nothing to keep in sync by hand.

A Figma spec sometimes hands you a color in a format your CSS doesn’t expect, most often OKLCH from a modern design tool, when Liquid settings store colors as hex:

{{ settings.color_primary | color_to_oklch }}
{{ settings.color_primary | color_to_hsl }}

Use these conversion filters when you need to match a specific format called for in a design spec, or when a CSS feature (like color-mix() in a specific color space) expects a particular format. For most theme CSS, the stored hex value works fine as-is and no conversion is needed.

Using color_brightness for contrast decisions

Section titled “Using color_brightness for contrast decisions”

color_brightness returns a 0–255 value, which is useful for a specific, narrow case: deciding at render time whether text on top of a merchant-chosen background should be light or dark.

{% assign brightness = settings.color_background | color_brightness %}
{% if brightness > 125 %}
{% assign text_color = '#000000' %}
{% else %}
{% assign text_color = '#ffffff' %}
{% endif %}

This is a runtime brightness check for picking between two fixed text colors, not a substitute for verifying real WCAG contrast ratios during development. See Color Accessibility & Contrast for the actual contrast ratio requirements and how to test them.

✅ Do❌ Don’t
Derive hover, active, and tint variants from one stored color with a filter, instead of adding a separate setting for each variant.Adding a separate setting for every derived shade (a “hover” setting, a “tint” setting) instead of deriving them from one base color. This creates settings that can silently drift out of sync.
Build derived CSS custom properties once, in a single place like theme.liquid or a shared snippet, rather than recalculating the same color_darken call in every section file that needs it.Hardcoding a hover or tint color as a literal hex value in CSS, instead of deriving it from the merchant’s chosen base color. This breaks the moment the merchant changes their brand color.
Only convert color formats (color_to_oklch, color_to_hsl, and so on) when a specific format is actually required. Leave colors in their stored format otherwise.Recalculating the same derived value in multiple files instead of computing it once into a shared CSS custom property.
Use color_brightness only for the light-text-vs-dark-text runtime decision it’s suited for. Verify actual contrast ratios separately; see Color Accessibility & Contrast.Treating color_brightness as a WCAG contrast checker. It returns a raw brightness number, not a contrast ratio against a specific background.
  • Derive shades, tints, and translucent variants from one stored color with color_darken, color_lighten, color_mix, and color_modify, instead of adding separate settings.
  • Convert formats with color_to_hex, color_to_hsl, color_to_rgb, color_to_oklch only when a specific format is actually required.
  • color_brightness returns 0–255, useful for a light-vs-dark text runtime decision, not a WCAG contrast check.
  • Compute derived CSS custom properties once, in a shared location, not per-section.