---
title: Reduce stylesheet count
description: >-
  Minimize the number of separate `<link>` stylesheet tags on a page to reduce
  expensive style recalculations that block interactions and hurt INP.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/reduce-stylesheet-count
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/reduce-stylesheet-count.md
api_name: liquid
---

# Reduce stylesheet count

Minimize the number of separate stylesheet links on a page to reduce expensive browser style recalculations that block user interactions and hurt INP.

***

## Why

Each separate stylesheet link requires browser parsing and application. URLs are downloaded only once, but the browser must parse and apply each stylesheet separately, which causes expensive style recalculations that severely impact INP (Interaction to Next Paint).

On collection pages with many product cards, each card rendering its own stylesheet can result in 100 to 1,150 separate stylesheets, causing style recalculation to take hundreds of milliseconds. This blocks interactions like opening menus, clicking products, or switching variants.

Measured improvements from production themes show that reducing stylesheets from 1,150 to 30 produced a 98 percent improvement in style recalculation time. Style recalculations happen throughout the page lifecycle, during HTML parsing, script execution, and especially during interactions, so fast recalculations are critical for good INP scores.

***

## How

### Add `skip_styles` parameter to snippets

For snippets rendered multiple times, such as product cards, loading spinners, and progress bars, add a `skip_styles` parameter:

## snippets/card-product.liquid

```liquid
{%- unless skip_styles -%}
  <link rel="stylesheet" href="{{ 'component-card.css' | asset_url }}">
{%- endunless -%}


<div class="card-product">
  {%- comment -%} Card markup {%- endcomment -%}
</div>
```

Usage in sections:

```liquid
{%- assign skip_card_product_styles = false -%}
{%- for product in collection.products limit: 12 -%}
  {% render 'card-product', product: product, skip_styles: skip_card_product_styles %}
  {%- assign skip_card_product_styles = true -%}
{%- endfor -%}
```

Result: 12 product cards load 1 stylesheet instead of 12, and 50 product cards load 1 stylesheet instead of 50.

**Render arguments can't contain comparisons:**

Liquid parses render arguments as name-value pairs, so a comparison like `skip_styles: forloop.index > 1` binds `skip_styles` to `forloop.index` and silently drops the `> 1`. The snippet receives a number, and every number is truthy in Liquid, so `{% unless skip_styles %}` never runs and the stylesheet never loads. Assign the comparison to a variable first, then pass the variable.

### Move global snippet styles to `base.css`

For snippets used across the entire theme, such as loading spinners, progress bars, and icons, move their CSS to `base.css` instead of including stylesheet links.

Candidates include:

* Loading spinners.
* Progress bars.
* Icon systems.
* Common UI components.
* Any snippet used site-wide.

### Consolidate related CSS files

Instead of separate CSS files for related components, combine them into single consolidated stylesheets.

Benefits:

* Better compression, because gzip and brotli are more effective on larger files.
* Fewer requests.
* Faster parsing, because one larger file is faster than many small files.

***

## Examples

### Real-world impact from the Trade theme

Before optimization: more than 1,150 separate stylesheet links, with duplicate `component-card.css` for every card and duplicate `loading-spinner.css` throughout the page.

After optimization: about 30 stylesheets, with `base.css` including the spinner and progress bar, `component-card.css` loaded once, and only unique stylesheets loaded.

Performance impact: style recalculation 98 percent faster, INP significantly improved, and menu opens and clicks no longer blocked.

### Product card snippet with `skip_styles`

## snippets/card-product.liquid

```liquid
{%- liquid
  unless skip_styles
    echo 'component-card.css' | asset_url | stylesheet_tag
    echo 'component-price.css' | asset_url | stylesheet_tag
  endunless
-%}


<div class="card-product">
  <a href="{{ product.url }}">
    {{ product.featured_image
      | image_url: width: 600
      | image_tag: loading: image_loading
    }}
    <h3>{{ product.title }}</h3>
    <p class="price">{{ product.price | money }}</p>
  </a>
</div>
```

Section using the snippet:

```liquid
{% paginate collection.products by 12 %}
  <div class="product-grid">
    {%- assign skip_card_product_styles = false -%}
    {%- for product in collection.products limit: 12 -%}
      {%- assign image_loading = 'lazy' -%}
      {%- if forloop.index <= 4 -%}
        {%- assign image_loading = 'eager' -%}
      {%- endif -%}


      {% render 'card-product',
        product: product,
        image_loading: image_loading,
        skip_styles: skip_card_product_styles
      %}


      {%- assign skip_card_product_styles = true -%}
    {%- endfor -%}
  </div>
{% endpaginate %}
```

Result: 12 cards load 1 stylesheet instead of 12. The first card loads styles, and the remaining 11 cards skip styles.

The `loading` argument of [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag) takes the string `'eager'` or `'lazy'`. `image_tag` copies it into the attribute as-is, so passing a boolean or a number emits an invalid `loading="true"` or `loading="1"`. If you omit `loading`, then `image_tag` sets it for you based on the section's position on the page.

### Product cards in collections

Problem: 50 product cards load 50 or more stylesheets.

Solution:

```liquid
{% paginate collection.products by 50 %}
  {%- assign skip_card_product_styles = false -%}
  {% for product in collection.products limit: 50 %}
    {% render 'card-product',
      product: product,
      skip_styles: skip_card_product_styles
    %}
    {%- assign skip_card_product_styles = true -%}
  {% endfor %}
{% endpaginate %}
```

Result: 50 product cards load 1 stylesheet.

### Global UI components

Problem: a loading spinner snippet with a stylesheet is used in the cart, product page, search, and other places.

Solution: move the `loading-spinner.css` contents to `base.css` and update the snippet to remove the stylesheet link.

### Consolidate related components

Problem: separate files for `product-card.css`, `product-price.css`, `product-badge.css`, and `product-rating.css`.

Solution: consolidate into a single `component-product.css` file with all related component styles.

***

## Testing

Measure stylesheet count with console:

```javascript
document.querySelectorAll("link[rel=stylesheet]").length
```

Targets:

* Collection page: fewer than 50 stylesheets.
* Product page: fewer than 30 stylesheets.
* Homepage: fewer than 40 stylesheets.

Problematic:

* Collection page: more than 100 stylesheets.
* Product page: more than 50 stylesheets.

[Chrome DevTools Performance panel](https://developer.chrome.com/docs/devtools/performance): record an interaction and look for **Recalculate Style** events, then compare before and after.

INP measurement: use the [Chrome DevTools Performance panel](https://developer.chrome.com/docs/devtools/performance) to record interactions and check the INP metric in the summary. Target under 200 ms.

Visual inspection: use the [Chrome DevTools Coverage tab](https://developer.chrome.com/docs/devtools/coverage) to identify unused CSS and duplicate stylesheets loading the same rules.

***

## References

* [Dawn PR #3509](https://github.com/Shopify/dawn/pull/3509): stylesheet reduction implementation (98 percent improvement).
* [Shopify CDN](https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform#shopify-cdn): automatic minification and compression.
* [`stylesheet_tag`](https://shopify.dev/docs/api/liquid/filters/stylesheet_tag) filter
* [`asset_url`](https://shopify.dev/docs/api/liquid/filters/asset_url) filter
* [Debounce and throttle event handlers](https://shopify.dev/docs/storefronts/themes/best-practices/performance/debounce-throttle-event-handlers)
* [Avoid nested renders](https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-nested-renders)

***
