MODERN CSS STYLING APPROACHES

Modern CSS Styling Approaches

CSS styling has evolved significantly, moving from traditional global stylesheets and independent style files to more modular, component-level approaches. Modern CSS focuses on scalability, reusability, and maintainability, helping developers create UI designs that are easier to manage and more predictable in large applications. This document outlines the evolution from traditional styling methods to modern CSS techniques.

Evolution of CSS Styling

  1. Global Stylesheets
    Early CSS styling typically involved a single global stylesheet for the entire application, where all styles were defined in one file or a few large files.

    • Pros: Simple to manage for small applications.
    • Cons: Difficult to maintain in large applications, prone to style conflicts, and lacks reusability.
  2. Independent Style Files (CSS per Component)
    With the growth of larger applications, developers started organizing CSS in separate files for each component or page to improve manageability.

    • Pros: Better organization compared to a single stylesheet.
    • Cons: Still lacks true isolation; components can inadvertently affect each other.
  3. Global Utility Classes
    In an effort to create reusable styles, developers used utility classes like .btn, .text-center, and .margin-10. While this was an improvement, global classes still led to conflicts and were often too restrictive.

    • Pros: Reusable classes, reduced duplication.
    • Cons: Hard to customize for specific components, and styles still risk global conflicts. Relying heavily on custom global classes can add significant mental overhead for developers, especially in larger projects with complex styling needs.

Modern CSS Approaches

Today, CSS is evolving toward modular, reusable, and scoped solutions that align well with component-based architecture. Here are the most popular modern CSS approaches:

1. CSS Modules

CSS Modules scope styles locally by default, which means classes and IDs are only applied to the component where they’re defined, helping avoid style conflicts in larger applications.

  • Benefits:
    • Avoids global CSS conflicts.
    • Styles are imported as objects, making them easy to reference within components.
  • Usage:
    • Commonly used with frameworks like React, Vue, and Next.js.
  • Resource: CSS Modules

In a React application CSS Modules would look like this

/* Button.module.css */
.button {
  background-color: blue;
}
import styles from "./Button.module.css";
<button className={styles.button}>Click Me</button>;

In Vue we would use the $style object

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

2. Localised style tags

Localised Styles in Raw HTML with Inline <style> Tags

While the HTML5 scoped attribute for <style scoped> tags was proposed and is still sometimes mentioned, it is not supported in modern browsers. However, you can achieve a similar “local” effect by using inline <style> tags with classes or IDs to limit the scope manually.

Example: Using a Localized <style> Tag within a Section

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Localized Styles Example</title>
  </head>
  <body>
    <section id="special-section">
      <!-- Localized styles only for this section -->
      <style>
        #special-section h2 {
          color: green;
          font-size: 24px;
        }
        #special-section p {
          font-style: italic;
          color: darkgreen;
        }
      </style>

      <h2>Styled Heading in Special Section</h2>
      <p>Only paragraphs within this section are styled.</p>
    </section>

    <!-- This section is unaffected by the local styles -->
    <section>
      <h2>This Heading is Not Styled</h2>
      <p>This paragraph is outside of the special section and remains unaffected.</p>
    </section>
  </body>
</html>

Explanation

<style> Tag Inside Section: Place a <style> tag within a specific section (like <section> or <div>) to contain styles relevant only to that part of the document. • Scoped Selectors (e.g., #special-section h2): By using an ID or a unique class for the section, you can target only the elements within that section, effectively creating scoped styles.

Benefits of Local <style> Tags in HTML

• Encapsulation: Styles only apply within the section they’re defined in, helping avoid global style conflicts. • Self-Contained Styling: This approach keeps styles close to the HTML they apply to, making it easier to maintain in smaller or self-contained HTML files.

3. Scoped Styles in Frameworks (Vue and Angular)

Frameworks like Vue and Angular allow for scoped CSS within single-file components, which automatically scopes styles to a particular component.

  • Benefits:
    • Prevents styles from leaking outside the component.
    • Simplifies CSS management in large applications.
  • Usage:
    • Vue: <style scoped> attribute in Vue SFCs.
    • Angular: Uses encapsulated view for styles by default.
  • Resource: Vue Scoped Styles

How Scoped Styles Work in Vue and Nuxt

In a Vue SFC, when you add the scoped attribute to the <style> tag, Vue’s compiler will:

  1. Generate unique attributes (like data-v-f137c5cf) for the component’s DOM elements.
  2. Automatically apply those attributes to styles, limiting them to the specific component instance.

This approach is similar to “local” or “scoped” styles but handled automatically without requiring inline <style> tags or manual scoping.

Example: Scoped Styles in a Nuxt Component

<template>
  <div class="example">Scoped styles only apply here.</div>
</template>

<script>
export default {
  name: "ExampleComponent"
};
</script>

<style scoped>
.example {
  align-items: baseline;
  font-size: 1.5rem;
  background-color: #242424;
  width: fit-content;
  border: 1px solid white;
}
</style>

Generated CSS in Nuxt/Vue

With the scoped attribute, Nuxt/Vue automatically generates unique data attributes, resulting in CSS that looks like this:

.example[data-v-f137c5cf] {
  align-items: baseline;
  font-size: 1.5rem;
  background-color: #242424;
  width: fit-content;
  border: 1px solid white;
}

4. Atomic CSS

Atomic CSS is a utility-first approach where styles are broken down into small, reusable utility classes (e.g., text-center, bg-blue). This method minimizes CSS bloat and enables rapid UI development.

  • Benefits:
    • High reusability and reduces CSS size.
    • Encourages consistent design by reusing the same utilities.
  • Popular Framework: Tailwind CSS
  • Resource: Tailwind CSS
<button class="bg-blue text-white p-2 rounded">Click Me</button>

5. CSS-in-JS

CSS-in-JS involves writing CSS directly within JavaScript files, allowing styles to be tightly coupled with component logic. It also provides the ability to use JavaScript variables and logic within styles, enhancing flexibility.

  • Benefits:
    • Co-locates styles with component logic.
    • Dynamically applies styles based on component state or props.
  • Popular Libraries: Styled Components, Emotion
  • Resource: Styled Components

In a React application it would typically look like this using styled-components

import styled from "styled-components";

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 10px;
`;

Dynamically setting styles in Vue with a :style binding based on props is similar to the principles of CSS-in-JS but adapted for Vue’s component system. It allows you to apply styles inline, using computed properties and reactive state without needing a dedicated CSS-in-JS library.

In Vue, this approach works particularly well when:

  1. You need styles that vary based on component props or state.
  2. You want to avoid hardcoded inline styles and maintain a level of reactivity and readability.

Example: Dynamic Styling with :style Binding and Computed Properties

Here’s a more detailed example using dynamic props such as display, gap, and padding for a <FlexBox> component that applies these styles based on passed props.

<template>
  <div :style="computedStyles">
    <slot />
  </div>
</template>

<script>
export default {
  props: {
    display: {
      type: String,
      default: "flex"
    },
    gap: {
      type: String,
      default: "16px"
    },
    padding: {
      type: String,
      default: "8px"
    },
    alignItems: {
      type: String,
      default: "center"
    },
    justifyContent: {
      type: String,
      default: "center"
    }
  },
  computed: {
    computedStyles() {
      return {
        display: this.display,
        gap: this.gap,
        padding: this.padding,
        alignItems: this.alignItems,
        justifyContent: this.justifyContent
      };
    }
  }
};
</script>

<style scoped>
/* Optionally define base styles for fallback or custom adjustments */
</style>

6. Design Systems and Component Libraries

Design systems and component libraries incorporate reusable components and standardized styling across an entire application, enabling consistency and improving scalability.

  • Benefits:
    • Enforces consistent UI design.
    • Reduces design debt and minimizes styling inconsistencies.
  • Popular Tools: Storybook, Figma, Chromatic
  • Resource: Storybook for Design Systems

7. BEM (Block Element Modifier)

BEM is a naming convention that structures CSS in a way that is readable, predictable, and modular. Each component has its own "block" name, with modifiers and elements organized in a clear hierarchy.

  • Benefits:
    • Avoids naming conflicts and organizes styles predictably.
    • Helps structure complex UIs.
  • Resource: BEM Methodology
.button--primary {
  /* primary button */
}
.button--secondary {
  /* secondary button */
}
.button__icon {
  /* icon inside button */
}

8. CSS Variables

CSS styling has evolved significantly, with CSS variables (custom properties) emerging as a powerful way to manage reusable values like colors, font sizes, and spacing across components. By defining variables in a central location (often the :root), CSS variables allow for consistent, scalable styling that can adapt to themes, layouts, and user preferences. This document explores CSS variables as part of modern CSS techniques.

Defining CSS Variables

CSS variables are typically defined at the :root level, making them globally accessible. This enables centralised styling control, which is especially useful for theme management and consistent design across a large application.

Example of CSS Variables in :root
:root {
  /* Color variables */
  --color-primary: #007bff;
  --color-secondary: #6c757d;
  --color-background: #f8f9fa;

  /* Font and spacing variables */
  --font-size-base: 16px;
  --spacing-small: 8px;
  --spacing-medium: 16px;
  --spacing-large: 24px;
}

Benefits of CSS Variables

  • Consistency: Centralised values prevent style inconsistencies across components.
  • Dynamic Theming: Values can be updated dynamically to enable light and dark modes or other themes.
  • Responsiveness: Variables can adapt based on media queries or user preferences.
.button {
  background-color: var(--color-primary);
  color: #fff;
  font-size: var(--font-size-base);
  padding: var(--spacing-small) var(--spacing-medium);
}

.button--secondary {
  background-color: var(--color-secondary);
}

To create maintainable, scalable, and consistent styling in modern applications, the following practices are recommended:

  1. Use Scoped or Modular CSS
    Isolate styles at the component level using scoped styles (Vue), CSS Modules, or CSS-in-JS. This reduces conflicts and makes components more predictable.

  2. Adopt Utility-First CSS for Speed
    Consider Tailwind CSS or another atomic CSS approach for rapid development, especially in applications where style consistency is key.

  3. Create a Design System
    Develop a standardized design system or component library for consistent styling across the application. Tools like Storybook are great for documenting and testing these components.

  4. Use CSS-in-JS for Dynamic Styling
    For components with complex, state-based styling requirements, CSS-in-JS can help by allowing inline logic and variables.

Summary

Modern CSS approaches provide flexibility, modularity, and scalability, enabling efficient and maintainable styling for component-driven applications.