# Color system

> Build a reusable color system for a Shoplazza Theme with role-based color settings and CSS variables, so merchants restyle the whole storefront in one place.

Color is one of the strongest levers a merchant has to express their brand. A well-designed color system lets merchants restyle the entire storefront from theme settings — change one color, and every section follows. This guide shows how to build that system with `color` settings and CSS variables.

## Define color settings by role

Add [`color` settings](/docs/theme/architecture/settings/input-settings#color) to your `config/settings_schema.json`, one per **role** — the purpose a color serves — rather than one per element:

```json
{
  "type": "color",
  "id": "color_body_text",
  "label": {
    "en-US": "Body text",
    "zh-CN": "正文文字"
  },
  "default": "#292929"
}
```

Keep the set small. Roles like background, body text, headings, buttons, and borders cover most of a theme; when many elements share one role, merchants can restyle the store with a handful of pickers instead of hunting through dozens. Add element-specific colors only where merchants genuinely need independent control (for example, an announcement bar), and group them under clear headings in the settings sidebar.

Name the IDs predictably — a shared prefix such as `color_` keeps them easy to find in Liquid and lets you process them in a loop (see below).

## Normalize the values

The color picker stores values in two formats, and Liquid outputs them exactly as stored: `#FFFFFF` when the merchant picks an opaque color, and `rgba(41, 41, 41, 1)` when they touch the transparency input. Both are valid CSS, so if you only ever print the value into a stylesheet, you can use it as is.

Normalize when your CSS logic assumes one format — for example, when you derive variants from a base color. The [`color_to_hex`](/docs/theme/references/liquid/filters/color#color_to_hex) filter converts any CSS color string to `hex6`:

```liquid
{% assign color_body_text = settings.color_body_text | default: '#292929' %}
{% unless color_body_text contains '#' %}
  {% assign color_body_text = color_body_text | color_to_hex %}
{% endunless %}
```

The `default` filter keeps the theme working before the merchant saves settings for the first time.

:::warning
`color_to_hex` drops the alpha component: `rgba(41, 41, 41, 0.5)` becomes `#292929`. Where the merchant's transparency choice matters — overlays, scrims, translucent headers — use the stored value directly instead of normalizing it.
:::

## Output the palette as CSS variables

Convert the settings into CSS variables once, in a snippet rendered from your layout, so the whole theme reads colors from a single place:

```liquid
<style>
  :root {
    --color-body-text: {{ color_body_text }};
    --color-heading-text: {{ color_heading_text }};
    --color-btn-bg: {{ color_btn_bg }};
    --color-btn-text: {{ color_btn_text }};
    --color-border: {{ color_border }};
  }
</style>
```

If your color settings share the `color_` prefix, you can also generate the variables in a loop instead of listing each one:

```liquid
{% for s in settings %}
  {% if s[0] contains 'color_' %}
    --{{ s[0] | replace: '_', '-' }}: {{ s[1] }};
  {% endif %}
{% endfor %}
```

## Consume the variables everywhere

In sections, snippets, and stylesheets, reference the variables instead of raw values:

```css
.product-card__title {
  color: var(--color-heading-text);
}
.btn--primary {
  background: var(--color-btn-bg);
  color: var(--color-btn-text);
}
```

When the merchant changes a color in the theme editor, every element wearing that role updates together. Hardcoded colors are acceptable only for elements whose contrast doesn't depend on merchant choices — anything a merchant can recolor should go through a variable.

## Keep contrast accessible

A color system also concentrates your accessibility risk: one bad pairing of text and background colors spreads everywhere. Choose defaults that meet the contrast levels in [accessibility best practices](/docs/theme/best-practices/accessibility), and avoid pairing two merchant-configurable colors on elements where contrast is critical without sensible defaults.
