One, foreword

With the iteration of the product, it was found that there were many problems in the existing product architecture, such as performance, business, team collaboration, etc. As an old project, it could not be further optimized and improved, so our team planned to carry out a complete reconstruction of the whole project this time.

It took about a week to sort out the relevant design scheme. This article documents the final solutions for page style CSS in the project, but of course, it should be noted that these solutions are ultimately based on actual business scenarios, customer needs, and team collaboration, and are not applicable to every other project.

2. Technology stack

  • Old project technology stack: Vue2 + Less + Webpack+ Component library (in-house development)
  • New Project Technology Stack: Vue3 + CSS + TypeScript + vite + Ant Design

Third, the problems faced

All solutions are discussed based on the actual problems facing the project, as follows:

1. With the continuous improvement of product functions, the size of style files increases increments, which will slow down the packaging speed and performance to a certain extent;

2. New requirements involve online peels, which are not elegantly compatible with old projects. We can do it, but it costs a lot.

3. In teamwork, style files are written in disorder, and everyone has his or her own style, which leads to code redundancy and unable to be better maintained in the later period.

4. CSS Solution

Finally, we decided to take a conceptual approach to the style information used in the project. Of course, you might have a few questions. CSS just writes the class style and then hangs the class into the corresponding HTML structure.

Why layering? What’s the point of layering? How do you layer? What is the basis for stratification?

1. Difficulties with CSS – Problems with using CSS

Before addressing these issues, we need to explore some of the CSS issues we encounter in our daily projects.

  • Problem # 1: Different HTML structure, same CSS style
<! -- Component 1 -->
<div class="article-preview">
  <img class="article-preview__image" src="./image.png" alt="">
  <div class="article-preview__content">
    <h2 class="article-preview__title">Article title: XXXXXXX</h2>
    <p class="article-preview__body">Content: XXXXXX XXXXXXXXXXXXXX, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.</p>
  </div>
</div>
Copy the code

As above, I wrote a component for article preview, and all classes are written in Block Element Modifer. But because of the increased need to write a component with the same CSS style as article preview, let’s call it the resume component.

div>
  <imgsrc="./header.png" alt="">
  <div>
    <h2>Name: Fawn</h2>
    <p>Personal profile: Proficient in VUE development, XXXXXXXXXXXXX</p>
  </div>
</div>
Copy the code

Although the content of the HTML structure above is different, the CSS style is exactly the same. Since the article preview component class name is semantic (corresponding to the relevant article preview), if we use the article preview class directly to the resume component, we violate the principle of semantics.

So we have to copy the same style as all the classes in the article preview, but change the corresponding class name to the semantically relevant class name of the resume.

<div class="author-bio">
  <img class="author-bio__image" src="./header.png" alt="">
  <div class="author-bio__content">
    <h2 class="author-bio__name">Name: Fawn</h2>
    <p class="author-bio__body">Personal profile: Proficient in VUE development, XXXXXXXXXXXXX</p>
  </div>
</div>
Copy the code

Then the problem is that you have to copy the same style every time you encounter the same style, resulting in code redundancy and an increase in the size of the project style file.

At this point, we might want to change the class to a generic semantic one, and call the class media-card.

<div class="media-card">
  <img class="media-card__image" src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">Article title: XXXXXXX</h2>
    <p class="media-card__body">Content: XXXXXX XXXXXXXXXXXXXX, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.</p>
  </div>
</div>

<div class="media-card">
  <img class="media-card__image" src="https://i.vimeocdn.com/video/585037904_1280x720.webp" alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">Name: Fawn</h2>
    <p class="media-card__body">Personal profile: Proficient in VUE development, XXXXXXXXXXXXX</p>
  </div>
</div>
Copy the code

By unifying the class names above, we can reuse CSS styles rather than relying on a specific HTML structure.

  • Problem 2: Same HTML structure, different CSS styles

That’s a lot of class reuse, but the problem is, if one day my boss asks me to add a special style to the border of the article preview, but the resume component doesn’t change the style, We can only use inline styles (which violate the principle of low coupling between HTML and CSS) or declare a class class to override them (reduced flexibility, code redundancy).

Because the HTML structure is so dependent on CSS, and although classes can be reused, HTML is not flexible enough to be designed in particular styles, this solution seems inflexible. Can there be a way to reuse CSS styles while also doing special things to HTML?

For the development of CSS, here is a reference to the personal use of CSS written by Adam Wathan, as well as the advantages and disadvantages of each CSS writing.

Adamwathan. Me/CSS – Utility…

English translation: tailwindchina.com/translation…

This article was also written by Adam Wathan, author of the Tailwind CSS project. For more information about Tailwind, check out docs.tailwindchina.com/.

2,CSSThe latest design idea ofTailwind CSS

Tailwind CSS was a project we came across while exploring solutions, and based on its design ideas, we further improved our CSS solution for the project.

Take a look at an official Tailewind CSS example.

<button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700">
  Click me
</button>/** /.py-2 {padding-top: 0.5rem; Padding-bottom: 0.5rem}. Px-4 {padding-left: 1rem; padding-right: 1rem } .font-semibold { font-weight: 600 } .rounded-lg { border-radius: 0.rem}. Shadow-md {--tw-shadow: 0 4px 6px-1px rgba(0, 0, 0, 0.1), 0 2px 4px-1px rgba(0, 0, 0, 0.06); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) } .bg-green-500 { --tw-bg-opacity: 1; background-color: rgba(16, 185, 129, var(--tw-bg-opacity)) }Copy the code

In terms of usage, it uses the same style combination of class. On the surface, it is the same as ours. However, after careful analysis, it will be found that the attributes in the class class are less granular. The reason for this is to keep the class “flexible” and “reusable” — the more properties declared in a class, the harder it is to reuse them.

That is, each class has as few attributes as possible, while the different combinations of attributes correspond to the class name, preserving the semantic specifications of CSS.

Based on the above in-depth understanding of CSS semantics, I have summarized several features of semantic CSS.

  • Semantic class — reusability
/***** font-weight *****/ .font-weight-normal { font-weight: normal; } .font-weight-bold { font-weight: bold; } .font-weight-100 { font-weight: 100; } .font-weight-200 { font-weight: 200; } .font-weight-300 { font-weight: 300; } .font-weight-400 { font-weight: 400; }... /***** margin *****/ .margin-auto { margin: auto; } .margin-0 { margin: 0; } .margin-5 { margin: 5px; } .margin-10 { margin: 10px; }...Copy the code
  • Semantic class — limitations

As we mentioned above, the biggest problem with semantic classes is not to declare too many CSS attributes in each class, which can make the class difficult to reuse and much less flexible.

If you want to use the above design ideas, make sure that the more properties declared in a class, the harder it is to reuse that class.

3, the ultimateCSSDesign scheme

In the end, we did not directly use Tailwind CSS project because of the special skin changing requirements of the project. Although Tailwind CSS can be used to achieve skin changing, it is not elegant and convenient. In the following, we will talk about specific skin changing solutions. Here is a detailed introduction to our design ideas based on Tailwind CSS and the flexible use of CSS variables to determine the ultimate CSS solution.

At the beginning of this article, we mentioned the concept of layering. Yes, there are three layers of CSS: atomic layer, variable application layer, and class application layer. We will analyze the role of each layer.

  • Variable atomicLayer

We major in this layer statement of CSS atomized variables, all of the pages to use the “size” and “color”, “animation” and other attribute values (color, size, margin size, etc.), we use all of the pages related to color, size, animation attribute value should be in the statement.

/** Atomic layer **/<! -- atomicLayer.css -->
:root {
  /***** color *****/
  /* transparent */
  --color-transparent: transparent;

  /* black */
  --color-black: # 000000;
  --color-black-1rgba(0.0.0.0.85);
  --color-black-2rgba(0.0.0.0.45);
  --color-black-3rgba(0.0.0.0.25);
  --color-black-4rgba(0.0.0.0.15);
  --color-black-5rgba(0.0.0.0.018); ./* white */
  --color-white: #ffffff;
  --color-white-1#f5f7fa;
  --color-white-2#fafafa; ./* blue */
  --color-blue-1#1890ff;
  --color-blue-2#40a9ff;
  --color-blue-3#e6f7ff;
  --color-blue-4rgba(24.144.255.0.2); ./* gray */
  --color-gray-1#d9d9d9; ./* red */
  --color-red-1#ff4d4f;
  --color-red-2rgba(255.77.79.0.2);


  /***** font-size *****/
  --font-size-112px;
  --font-size-214px;
  --font-size-316px;
  --font-size-418px;


  /***** border-radius *****/
  --border-radius-12px;
  --border-radius-24px;
  --border-radius-36px;
  --border-radius-48px;


  /***** transition-duration *****/
  --transition-duration-10.15 s;
  --transition-duration-20.3 s;
  --transition-duration-31s;


  /***** animation-duration *****/
  --animation-duration-10.15 s;
  --animation-duration-20.3 s;
  --animation-duration-31s;
}
Copy the code
  • Variable applicationLayer

This layer is applied as the upper layer of the atomic layer, in which all relevant values are taken from the variable atomic layer, and the variable atomic layer without semantics is attached to a layer of semantics. Of course, this layer also has a special role, that is, the subsequent skin change scheme, is to change the variables of the layer to achieve the effect of skin.

/** * atomic application layer */

:root {
	/***** color *****/
	--primary-color: var(--color-blue-1);
	--secondary-color: var(--color-blue-2);
	--third-color: var(--color-blue-3);
	--error-color: var(--color-red-1);

	/* text */
	--text-color: var(--color-black-1);
	--text-hover-color: var(--secondary-color);
	--text-disabled-color: var(--color-black-3);
	--text-error-color: var(--color-red-1);

	/* button */
	--btn-primary-color: var(--color-white);
	--btn-primary-hover-color: var(--color-white);
	--btn-primary-disabled-color: var(--text-disabled-color);
	--btn-plain-color: var(--text-color);
	--btn-plain-hover-color: var(--text-hover-color);
	--btn-plain-disabled-color: var(--text-disabled-color);
	--btn-text-color: var(--text-color);
	--btn-text-hover-color: var(--text-hover-color);
	--btn-text-disabled-color: var(--text-disabled-color);
	--btn-link-color: var(--primary-color);
	--btn-link-hover-color: var(--secondary-color);
	--btn-link-disabled-color: var(--text-disabled-color);

	/* tab */
	--tab-color: var(--text-color);
	--tab-hover-color: var(--secondary-color);
	--tab-actived-color: var(--primary-color);
	--tab-disabled-color: var(--text-disabled-color);

	/* menu */
	--menu-color: var(--text-color);
	--menu-hover-color: var(--primary-color);
	--menu-actived-color: var(--primary-color);
	--menu-disabled-color: var(--text-disabled-color);


	/***** bg-color *****/
	/* body */
	--body-bg-color: var(--color-white-1);
	--common-bg-color: var(--color-white-1);

	/* btn */
	--btn-primary-bg-color: var(--primary-color);
	--btn-primary-hover-bg-color: var(--secondary-color);
	--btn-primary-disabled-bg-color: var(--color-white-1);
	--btn-plain-disabled-bg-color: var(--color-white-1);
	--btn-text-hover-bg-color: var(--color-black-5);
	--btn-text-disabled-bg-color: var(--color-transparent);
	--btn-link-bg-color: var(--color-transparent);
	--btn-link-hover-bg-color: var(--color-transparent);
	--btn-link-disabled-bg-color: var(--color-transparent);

	/* tab */
	--tab-card-bg-color: var(--color-white-2);
	--tab-card-actived-bg-color: var(--color-white);

	/* table */
	--table-header-bg-color: var(--color-white-2);
	--table-row-hover-bg-color: var(--third-color);
	--table-row-striped-bg-color: var(--color-white-2);

	/* select */
	--select-hover-bg-color: var(--color-white-2);
	--select-selected-bg-color: var(--third-color);

	/* menu */
	--menu-item-selected-bg-color: var(--third-color);

	/* dialog */
	--dialog-mask-bg-color: var(--color-black-4);


	/***** border-color *****/
	--border-color: var(--color-gray-1);
	--border-hover-color: var(--secondary-color);
	--border-disabled-color: var(--color-gray-1);
	--border-error-color: var(--error-color);

	/* btn */
	--btn-primary-border-color: var(--primary-color);
	--btn-primary-hover-border-color: var(--secondary-color);
	--btn-primary-disabled-border-color: var(--border-color);
	--btn-plain-border-color: var(--border-color);
	--btn-plain-hover-border-color: var(--secondary-color);
	--btn-plain-disabled-border-color: var(--border-color);
	--btn-text-border-color: var(--color-transparent);
	--btn-text-hover-border-color: var(--color-transparent);
	--btn-text-disabled-border-color: var(--color-transparent);
	--btn-link-border-color: var(--color-transparent);
	--btn-link-hover-border-color: var(--color-transparent);
	--btn-link-disabled-border-color: var(--color-transparent);


	/***** box-shadow-color *****/
	--box-shadow-color: var(--color-black-4);

	/* input */
	--input-focus-box-shadow-color: var(--color-blue-4);
	--input-error-focus-box-shadow-color: var(--color-red-2);


	/***** font-size *****/
	--font-size-mini: var(--font-size-1);
	--font-size-small: var(--font-size-2);
	--font-size-middle: var(--font-size-3);
	--font-size-large: var(--font-size-4);

	/* text */
	--text-font-size: var(--font-size-mini);

	/* title */
	--title-font-size: var(--font-size-large);


	/***** border-radius *****/
	--border-radius-mini: var(--border-radius-1);
	--border-radius-small: var(--border-radius-2);
	--border-radius-middle: var(--border-radius-3);
	--border-radius-large: var(--border-radius-4);

	--border-radius: var(--border-radius-small);


	/***** transition-duration *****/
	--transition-duration-fast: var(--transition-duration-1);
	--transition-duration-normal: var(--transition-duration-2);
	--transition-duration-slow: var(--transition-duration-3);

	--transition-duration: var(--transition-duration-fast);


	/***** animation-duration *****/
	--animation-duration-fast: var(--animation-duration-1);
	--animation-duration-normal: var(--animation-duration-2);
	--animation-duration-slow: var(--animation-duration-3);

	--animation-duration: var(--animation-duration-fast);
}
Copy the code
  • Class applicationLayerClasses

In the class application layer, we write some specific styles about the common class (common.css), component-dependent class (component.css), other classes (other.css), etc. We will describe the contents and functions of these files in detail.

  • Common style — common.css

The class styles in common.css are similar to the minimum granularity style classes declared in Tailwind CSS, but unlike Tailwind CSS, all of our variable values are taken from the “atomic application layer.”

/***** color *****/
/* text */
.text-color {
  colorvar(--text-color);
}
.hover:text-color:hover {
  --text-color: var(--text-hover-color);
}
.text-disabled-color..text-disabled-color:hover {
  --text-colorvar(--text-disabled-color);
}
.text-error-color..text-error-color:hover {
  colorvar(--text-error-color);
}

/***** border-color *****/
.border-color {
  border-colorvar(--border-color);
}
.hover:border-color:hover {
  --border-color: var(--border-hover-color);
}
.border-disabled-color..border-disabled-color:hover{-border-colorvar(--border-hover-color);
}
.border-error-color..border-error-color:hover{-border-colorvar(--border-error-color);
}

/***** box-shadow *****/
.box-shadow {
  box-shadow0 2px 8px var(--box-shadow-color);
}

/***** font-size *****/
.font-size-mini {
  font-sizevar(--font-size-mini);
}
.font-size-small {
  font-sizevar(--font-size-small);
}
.font-size-middle {
  font-sizevar(--font-size-middle);
}
.font-size-large {
  font-sizevar(--font-size-large); }...Copy the code

Tailwind CSS attribute values are written dead values, as follows:

<! --border color -->
.border-white {
  --tw-border-opacity1;
  border-colorrgba(255.255.255.var(--tw-border-opacity))
}

.border-gray-50 {
  --tw-border-opacity1;
  border-colorrgba(249.250.251.var(--tw-border-opacity))
}

.border-gray-100 {
  --tw-border-opacity1;
  border-colorrgba(243.244.246.var(--tw-border-opacity))
}

.border-gray-200 {
  --tw-border-opacity1;
  border-colorrgba(229.231.235.var(--tw-border-opacity))
}

.border-gray-300 {
  --tw-border-opacity1;
  border-colorrgba(209.213.219.var(--tw-border-opacity)) } <! --font-size -->

.text-xs {
  font-size0.75 rem;
  line-height1rem
}

.text-sm {
  font-size0.875 rem;
  line-height1.25 rem
}

.text-base {
  font-size1rem;
  line-height1.5 rem
}

.text-lg {
  font-size1.125 rem;
  line-height1.75 rem}...Copy the code

The purpose of this design is to directly skin online by covering the variable values of the atomic application layer, which will be discussed in the following chapter.

  • Component-dependent — component.css

Since we are using the Ant Design component library, we need to build our own wheels considering that the future business may not be supported by the existing component library, so all the application layer classes related to the component library are written in this file.

  • Other classes — other.css

Other. CSS is also a generic class, but the reason why it is not written to common.css is that the class in other. CSS is the smallest functional class made up of multiple attributes. The following are commonly used:

<! -- other.css -- >

/* Vertical center */
.flex-middle {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Redundant text... * /
.text-ellipsis {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
Copy the code

Common.css is the smallest function class for a single property, so here we make a separate store between the two.

Ok, here we finished sharing the CSS layered structure, is not very simple and clear, in order to further experience the layered and Tailwind CSS design ideas, we will do a Demo, experience a pleasant development, by the way, share the final online skin scheme.

4. Ultimate skin change solution

At the beginning, our team put forward a lot of skin changing schemes. Through constant discussion and communication within the group, we finally agreed on the final skin changing scheme.

We finally adopted the solution, not to say that other changes skin solution is not good, but our team according to the problem before and existing business requirements, specific project environment and product demand, coupled with the influence of artificial factors, decided the same problem, there will be a different solution.

Respectively from the following three aspects is what, how, why to introduce the use of this skin scheme.

  • Skin change principle — what

We use the principle of skinning by changing the value of the CSS variable in the class corresponding to the page style.

Note that we are changing the CSS variables in the atomic application layer, not the atomic variables, because if we change the atomic layer, the lowest variable value, then many elements in the page that do not need to be skinned are also changed, which is why we designed the atomic application layer.

So when we write pages that involve skin elements, the CSS values of the variables involved in skin should be retrieved from the atomic application layer.

There is a question, we use the third-party component library Ant Design, related to the component library skin how to achieve? This is also a defect in our skinning scheme. The solution is to make a class overlay on the upper style of all components in the component library. The attribute values in the covered class are replaced by CSS variables in our atomic application layer, so as to realize the skinning of the component library.

Although the workload in the early stage is very heavy, the subsequent maintenance and use is free of mental burden, which is our team’s choice of this scheme.

  • How did it work — How

Here I have written a Demo and uploaded it to Github. The project address is github.com/luxiangqian…

App.vue

<! -- * @Author: xiaolu * @Date2021-08-22 14:34:41
 * @LastEditors: xiaolu
 * @LastEditTime: 2021-08-26 09:33:00
 * @Description:
-->
<template>
  <div class="height-100% bg-page">
    <a-button
      class="margin-left-25 margin-top-25 box-shadow font-size-middle border-radius-large"
      @click="changeTheme"
    >Avi button</a-button>
    <div class="width-100% height-100%">
      <div>
        <div class="font-size-middle margin-left-25 margin-top-25">Font color:</div>
        <div class="width-100% flex-around margin-top-10">
          <div
            v-for="(item,index) in fontColors"
            :key="index"
            :class="['font-size-large',item.color]"
          >
            {{ item.name }}
          </div>
        </div>
        <div class="font-size-middle margin-left-25 margin-top-20">Font size:</div>
        <div class="width-100% flex-around margin-top-10">
          <div
            v-for="(item,index) in fontSizes"
            :key="index"
            :class="[item.fontSize]"
          >
            {{ item.name }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>

import { ref,reactive } from 'vue'

let flag = true

const fontColors = reactive([
  / / {
  // name: 'body font color ',
  // color: 'text-color'
  // },
  {
    name'Disable font color'.color'text-disabled-color'
  },
  {
    name'Wrong font color'.color'text-error-color'}])const fontSizes = reactive([
  {
    name'the mini size'.fontSize'font-size-mini'
  },
  {
    name'small font.fontSize'font-size-small'
  },
  / / {
  // name: 'middle ',
  // fontSize: 'font-size-middle'
  // },
  {
    name'large font.fontSize'font-size-large'}])function changeTheme(){
  if(flag){
    document.documentElement.setAttribute('data-theme'.'theme')}else{
    document.documentElement.setAttribute('data-theme'.' ') } flag = ! flag }</script>
Copy the code

The attribute values of the skin calSS mentioned in the above page are CSS variables taken from the atomic application layer. Let’s declare the new skinning CSS variable (replace the atomic application layer)

theme.css

:root[data-theme="theme"]{
  --btn-plain-border-color#f30606;
  --btn-plain-color#00ff00;

  --btn-plain-hover-border-colorrgb(255.115.0);
  --btn-plain-hover-colorrgb(255.115.0);
  --body-bg-color#cccccc;

  /* Font color */

  /* --text-color:rgb(35, 243, 8) ; * /
  --text-disabled-colorrgb(23.19.231);
  --text-error-colorrgb(216.21.216);

  /* Font size */
  --font-size-mini: 14px;
  --font-size-small: 16px;
  --font-size-large: 20px;
}
</script>
Copy the code

Expose the index.css entry to the page:

/ * * * * / CSS variables / * * / atomic layer @ import url (". / atomicLayer. CSS "); /* atomic applicationLayer */ @import url("./ applicationlayer.css "); /* Skinned CSS */ @import url("./theme.css");Copy the code

Start the project as follows:

Effect after skin change:

The above button button is based on the third-party library (Ant Design), which also achieves the effect of skin changing. The background color and font size are also online skin changing. For more component skin changing experience, you can download the Demo of this project on Github.

  • Use reason — why

There are many skin changing schemes, why not use other skin changing schemes, here are two classic questions, specific and everyone to talk about it.

  • Why not use class for peels?

If you use a Tailwind CSS, you will need to replace the corresponding class. The reason we can change the CSS variable values of the atomic application layer directly is that it is more flexible and convenient than using class overwriting.

Because a variable value can be referenced by multiple classes, and classes like Tailwind correspond to a single attribute value, you need to write more classes to override classes.

  • Why not use less for peels

The realization of less online skin is achieved by switching different variables. Considering performance, when the project is very large, the performance of online real-time compilation of LESS into CSS will decline. Considering this, the skin scheme of LESS is not adopted.

Five, the summary

Of course, each scheme has its own advantages and disadvantages, and ours is no exception.

According to the feedback from the team, using Tailwind CSS to write a class will be mentally taxing at the beginning. The cost of using Tailwind CSS is relatively high in the early stage, and the efficiency of setting up pages may be a little low. This is the biggest disadvantage of using Tailwind CSS, but this disadvantage can be gradually changed in the later stage. Once you are familiar with the class you are using, it will not be inefficient to use it, and this will depend on feedback from the team later on.

This part of the content is shared here, if you have any good questions or solutions, feel free to leave a comment below