Reducing CSS Size and Improving Performance in BLUE 3 Development

Ridho Anandamal

10 August 20240 minutes

Introduction

The development of BLUE 3 commenced in 2022, with a primary focus on swiftly releasing the Vue 2 to Vue 3 Migration Design System. As we transition into the next phase, our attention is now directed towards enhancing performance, particularly in terms of CSS efficiency.

The R&D Front-end Tech team raised concerns regarding the large sizes of our components. For instance:

  • The common Button component's CSS is approximately 23 KB.
  • The largest component, the Date Picker, weighs in at around 100 KB.

After nine months of exploration and development, we successfully reduced our CSS code by up to 54%. Additionally, we addressed style conflicts across various projects, as detailed below.

Exploration of Reduction Methods

Our exploration phase involved numerous ideas, which we documented on our Miro board.

Brainstorming Phase
Brainstorming Phase

Grouping Our Exploration

We categorized our exploration into four key sections:

Initial Size Reduction

Our first approach to size reduction included:

  • Eliminating scoped styles to remove attributes like data-v-xxxxxxxx from CSS selectors.
  • Discarding the dark mode theme, which immediately cut the size by 50%, although this feature is crucial to BLUE 3.
  • Transitioning from SASS variables to CSS variables (more details below).
  • Simplifying CSS variable names.

This stage was exploratory and not yet implemented.

Implementing CSS Variables as Tokens

We considered using CSS variables as global tokens. While the concept appeared promising, we ultimately decided against it due to the extensive effort required and its potential impact on our existing token system.

Initial Concept
Initial Concept

Understanding CSS Behavior: CSS Variables and Specificity

Before implementing significant changes, we needed a comprehensive understanding of CSS variables and specificity to prevent unexpected styling issues during refactoring.

CSS Variables, denoted as --variable-name, are dynamic and reusable values that can be declared globally and utilized throughout components. Unlike SASS variables, CSS variables are evaluated at runtime, enabling us to:

  • Dynamically switch themes (such as light and dark modes) without recompiling CSS.
  • Reduce redundancy and maintain consistent styles.
  • Easily override variables in specific scopes.

Specificity determines which CSS rule is applied when multiple rules target the same element. If not managed carefully, it can lead to styles that are challenging to override and maintain. Common issues we identified include:

  • Deeply nested selectors increase specificity, complicating overrides.
  • Overusing element selectors (like html, body) in conjunction with classes results in duplicated and cumbersome selectors.
  • Scoped styles in Vue (data-v-xxxx) introduce additional specificity, causing conflicts across projects.

SCSS Code Refactoring

We also refactored our SCSS code to minimize size and enhance maintainability:

  • Streamlined unnecessary HTML structures for more compact selectors.
  • Replaced @include SASS patterns with CSS variables to avoid generating unused styles.
  • Removed top-level HTML selectors (e.g., changing html:not(dark) to simply html) to - event duplicated selector generation.
  • Converted component imports to asset imports.
  • Shortened CSS variable names.
  • Added focus states ( and ).
  • Replaced imported components with slots (e.g., substituting a loader with a slot). Table Reduction after refactoring:

Result Explorationg
Result Explorationg

Utilizing PostCSS for Code Processing

To further automate and reduce CSS size, we implemented PostCSS processing. Initially, we experimented with an external library, postcss-variable-compress. While effective, it proved too aggressive, complicating visual QA for designers. To strike a balance between reduction and maintainability, we developed our own PostCSS plugin to:

  • Shorten CSS variable names.
  • Shorten CSS selector names.

With input from Glenn (our designer), we agreed on familiar abbreviations such as:

  • height → h
  • width → w
  • button → btn

Here’s a diagram illustrating how PostCSS operates

PostCSS Concept
PostCSS Concept

Deciding on Solutions

Our objective was to minimize the character length of CSS code. To achieve this, we opted for two solutions:

  • As previously mentioned, we lacked guidelines on SASS code performance during rapid development. Thus, we needed to refactor SCSS code to ensure the transformed CSS code is more lightweight.
  • Utilizing CSS variables to support the dark theme while reducing CSS code size.

Solution Implementation

We automated the CSS reduction process using our custom PostCSS plugin.

Before refactoring, we prepared visual test cases for all components (e.g., Button variants like color, border, and size), particularly for visual consistency. Instead of relying solely on manual QA (with assistance from designers), we opted for automated integration tests and image comparison checks, applying a strict 1% threshold for differences.

We repeated this process until no visual errors were detected.

For quick implementation, we focused on 13 smaller components, such as buttons, banners, badges, loaders, etc.

Proof of Concept and New Issues

We released a component and proof of concept within squads like Instore and Digital.

We discovered that the old button versions on the homepage header conflicted with the new CSS performance version, resulting in style clashes due to identical selectors.

Root Cause

The issue stemmed from Vue’s scoped styles, which add attributes like data-v-xxxxxxx.

In collaborative settings, this poses challenges when multiple components share the same selector but have differing styles.

Our Solution

We modified the Vite package to ensure that scoped style IDs are based on:

  • Version numbers (ensuring that 1.0.2 vs. 1.0.3 generates different IDs).
  • File content (any changes within <style> will update the ID).

Drawback: Updating Vite now necessitates updating our modified package as well.

Finalization

After resolving the scoped style issues, we re-rolled out the changes to the same squads, and there were no further style conflicts.

We then applied the same workflow across all components.

Conclusion

After nine months of dedicated work, we successfully reduced the total size of our 37 components from 1,139,489 bytes (1139.4 KB) to 521,213 bytes (521.2 KB)—a remarkable 54% reduction (excluding JavaScript)—while retaining the dark mode feature.

We also resolved style conflict issues on collaborative pages, ensuring encapsulated styles for each component version (starting from version 3.1.x).

Personal Takeaway

Personally, I gained a much deeper understanding of CSS behavior and how to write efficient SCSS code.

Special Thanks

I extend my gratitude to everyone who supported this project:

  • Cak Sony (UX Engineer Lead)
  • Clara (BLUE Engineer)
  • Glenn (BLUE Designer)
  • Pak Hidayat, Kak Herman, and Kevin (R&D Front-end Team)
  • Hariharan (Instore Front-end)
  • Sthyarooba (Digital Front-end)