Skip to content

Instantly share code, notes, and snippets.

@allen-munsch
Created November 7, 2024 15:20
Show Gist options
  • Save allen-munsch/7466c969db3c3d993f5455681da5fcab to your computer and use it in GitHub Desktop.
Save allen-munsch/7466c969db3c3d993f5455681da5fcab to your computer and use it in GitHub Desktop.
Cascading Stytle Sheets ( CSS ) methodology

Complete CSS Methodologies and Modern Practices Guide

This guide aims to be a high level panoply of methodologies, methods, practices related to CSS.

Feel free to make a comment if you find anything you'd really like added to it.

Table of Contents

Complete CSS Methodologies and Modern Practices Guide

Table of Contents

  1. Core Methodologies
  2. Modern Module Systems
  3. Modern Approaches and Tools
  4. Best Practices
  5. Modern Layout Patterns
  6. Performance and Optimization
  7. Methodology Comparison
  8. Modern CSS Features and Patterns
  9. Testing and Quality Assurance
  10. Design Systems Integration
  11. Micro-Frontend Architecture
  12. Modern Development Workflows
  13. Accessibility Implementation
  14. Internationalization
  15. Advanced Animation Patterns
  16. Security and Error Handling
  17. Monitoring and Analytics
  18. Documentation Standards

BEM (Block Element Modifier)

/* Block */
.card { }

/* Element */
.card__title { }
.card__image { }

/* Modifier */
.card--featured { }
.card__title--large { }

Effect: Makes CSS very predictable and maintainable. Usage: <div class="card card--featured">

OOCSS (Object-Oriented CSS)

/* Structure and skin are separated */
.button {                  /* Structure */
  padding: 10px 20px;
  border-radius: 5px;
}
.button-primary {          /* Skin */
  background: blue;
  color: white;
}

Effect: Reduces code duplication by separating structure from theme. Usage: <button class="button button-primary">

SMACSS (Scalable and Modular Architecture for CSS)

/* Base rules */
body { margin: 0; }

/* Layout rules */
.l-header { width: 100%; }

/* Module rules */
.nav { }
.nav-item { }

/* State rules */
.is-active { }

Effect: Organizes CSS into clear categories. Usage: <nav class="nav"><a class="nav-item is-active">

ITCSS (Inverted Triangle CSS)

/* Settings - variables */
:root {
  --primary-color: blue;
}

/* Tools - mixins/functions */
@mixin center { ... }

/* Generic - resets */
* { box-sizing: border-box; }

/* Elements - bare HTML elements */
a { color: var(--primary-color); }

/* Objects - non-cosmetic design patterns */
.media { display: flex; }

/* Components - specific UI components */
.product-card { }

/* Utilities - helpers and overrides */
.u-hidden { display: none; }

Effect: Organizes CSS from least to most specific, reducing specificity conflicts.

Atomic CSS

.p-1 { padding: 0.25rem; }
.m-2 { margin: 0.5rem; }
.flex { display: flex; }
.text-center { text-align: center; }

Effect: Creates small, single-purpose classes. Usage: <div class="p-1 m-2 flex text-center">

Native CSS Modules

Basic Import/Export

// styles.css
@import "./other-styles.css";
@layer components {
  .button { /* ... */ }
}

// main.js
import './styles.css';

CSS Modules with ESM

// Button.css
.button {
  background: blue;
}

// Button.js
import styles from './Button.css' assert { type: 'css' };
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];

Dynamic Imports

// Lazy-loaded component
async function loadComponent() {
  const [moduleJS, moduleCSS] = await Promise.all([
    import('./Component.js'),
    import('./Component.css', { assert: { type: 'css' } })
  ]);
  
  document.adoptedStyleSheets = [
    ...document.adoptedStyleSheets,
    moduleCSS.default
  ];
  return moduleJS.default;
}

Module Loading Patterns

ESM with CSS

// style.module.css
export const button = 'btn_xyz123';

// Component.js
import { button } from './style.module.css';
element.classList.add(button);

CommonJS Interop

// Old CJS style
const styles = require('./styles.css');

// Modern ESM style
import styles from './styles.css';

// Mixed environment
import cjsModule from 'legacy-module';
export const Component = () => {
  // Use both styles
};

Conditional Loading

// Dynamic import based on condition
if (condition) {
  import('./large-styles.css')
    .then(styles => {
      // Apply styles
    });
}

CSS Module Features

Scoped Styles

/* automatically scoped by bundler */
.title {
  color: blue;
}

/* compiles to */
.title_xyz123 {
  color: blue;
}

Composition

.base { /* ... */ }
.composed {
  composes: base;
  /* additional styles */
}

Global Styles

:global(.title) {
  /* not scoped */
}

CSS Modules

/* Component.module.css */
.title {
    color: blue;
}

/* Compiles to: */
.title_ax7yz9 {
    color: blue;
}

Effect: Automatically scopes CSS to components, preventing naming conflicts.

CSS-in-JS

const Button = styled.button`
    background: ${props => props.primary ? 'blue' : 'white'};
    color: ${props => props.primary ? 'white' : 'blue'};
    padding: 10px 20px;
`;

Effect: Enables dynamic, scoped styling in JavaScript components.

Utility-First CSS (Tailwind)

<div class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg">
    <h2 class="text-xl font-bold mb-4">Title</h2>
</div>

Effect: Provides comprehensive pre-built utilities for rapid development.

CSS Custom Properties

:root {
    --brand-color: #3490dc;
}
.button {
    background: var(--brand-color);
}

Effect: Enables dynamic theming and value changes.

Naming Conventions

.js-*  /* JavaScript hooks */
.c-*   /* Components */
.u-*   /* Utilities */
.t-*   /* Themes */

Architecture Principles

  • Single Responsibility Principle
  • Open/Closed Principle
  • DRY (Don't Repeat Yourself)
  • Separation of Concerns

Modern CSS Features

CSS Layers

@layer base, components, utilities;

@layer base {
  /* base styles */
}

@layer components {
  /* component styles */
}

Container Queries

@container sidebar (min-width: 400px) {
  .component {
    /* responsive to container */
  }
}

Cascade Layers

@layer framework {
  /* third-party styles */
}

@layer theme {
  /* theme overrides */
}

CSS Grid

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
}

Flexbox

.flex-container {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
}

Responsive Design

/* Mobile First */
.element {
    width: 100%;
}
@media (min-width: 768px) {
    .element {
        width: 50%;
    }
}

/* Container Queries */
@container (min-width: 400px) {
    .element {
        /* Styles based on parent container */
    }
}

Loading Strategies

Critical CSS

// Inline critical CSS
document.head.insertAdjacentHTML(
  'beforeend',
  `<style>${criticalCSS}</style>`
);

// Lazy load non-critical
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/non-critical.css';
document.head.appendChild(link);

Progressive Enhancement

// Base styles
import './base.css';

// Feature detection and enhancement
if ('containQueries' in CSS) {
  import('./container-queries.css');
}

Performance Optimization

// Route-based chunking
const routes = {
  home: {
    component: () => import('./Home'),
    styles: () => import('./home.css')
  },
  about: {
    component: () => import('./About'),
    styles: () => import('./about.css')
  }
};

// Preload on hover/visibility
function preloadRoute(route) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = route.styles;
  document.head.appendChild(link);
}

Build-time Integration

// vite.config.js
export default {
  css: {
    modules: {
      scopeBehaviour: 'local',
      localsConvention: 'camelCase',
      generateScopedName: '[name]_[local]_[hash:base64:5]'
    }
  }
}

Module Type Interop

// ESM
import styles from './styles.css';

// CJS
const styles = require('./styles.css');

// Dynamic ESM in CJS
const loadStyles = async () => {
  const { default: styles } = await import('./styles.css');
  return styles;
};

Problem-Solving Focus

  • BEM: Naming and scope problems
  • OOCSS: Reusability problems
  • SMACSS: Organization problems
  • ITCSS: Specificity problems
  • Atomic: Maintenance and scaling problems

Key Benefits

  • BEM: Component-based development, clear relationships
  • OOCSS: Reusability, reduced CSS size
  • SMACSS: Large project organization
  • ITCSS: Specificity management and scaling
  • Atomic: Rapid development, small file sizes

Performance Considerations

  • Specificity management
  • Critical CSS loading
  • CSS selector performance
  • File size optimization

:has() Selector Patterns

/* Parent selector patterns */
.card:has(img) {
  padding: 0;
}

/* Complex relational queries */
.form-field:has(input:invalid) {
  border-color: red;
}

/* Sibling combinations */
.section:has(+ .section) {
  margin-bottom: 2rem;
}

/* Multiple conditions */
article:has(h2):has(img) {
  grid-template: "heading image" auto / 1fr 1fr;
}

Scroll-Driven Animations

@keyframes parallax {
  from { transform: translateZ(0); }
  to { transform: translateZ(-500px); }
}

.parallax-element {
  animation: parallax linear;
  animation-timeline: scroll();
  animation-range: entry 50% cover 50%;
}

/* Progressive enhancement */
@supports (animation-timeline: scroll()) {
  .scroll-driven {
    animation: fade-in linear;
    animation-timeline: view();
    animation-range: entry 20% cover 40%;
  }
}

View Transitions API

/* Define transitions */
::view-transition-old(root) {
  animation: fade-out 0.5s ease-out;
}

::view-transition-new(root) {
  animation: fade-in 0.5s ease-out;
}

/* Custom named transitions */
.hero-image {
  view-transition-name: hero;
}

/* JavaScript API usage */
document.startViewTransition(() => {
  // DOM updates here
});

Modern Color Handling

/* Modern color spaces */
.element {
  /* OKLCH for better perceptual uniformity */
  color: oklch(67% 0.2 230);
  
  /* Color mixing */
  background: color-mix(in oklch, var(--brand-color) 75%, var(--accent-color));
  
  /* Relative color syntax */
  border-color: rgb(from var(--brand-color) r g b / 50%);
}

/* Color contrast adaptation */
.adaptive-text {
  color: color-contrast(var(--background-color) vs 
    black, white, #777777);
}

CSS Containment

/* Containment types */
.card {
  contain: content;
  contain-intrinsic-size: 200px;
}

.scrollable-area {
  contain: paint;
}

/* Layout containment */
.widget {
  contain: layout;
  contain-intrinsic-width: 300px;
  contain-intrinsic-height: 200px;
}

Content Visibility

/* Automatic content visibility */
.section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}

/* With progressive enhancement */
@supports (content-visibility: auto) {
  .lazy-section {
    content-visibility: auto;
    contain-intrinsic-size: 0 300px;
  }
}

Loading Optimizations

/* Priority hints */
<link rel="stylesheet" href="/critical.css" fetchpriority="high">
<link rel="stylesheet" href="/non-critical.css" fetchpriority="low">

/* Resource hints */
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>
<link rel="modulepreload" href="/components/header.js">

Visual Regression Testing

// Jest with Jest-image-snapshot
describe('Component Visual Regression', () => {
  it('matches snapshot', async () => {
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      failureThreshold: 0.01,
      failureThresholdType: 'percent'
    });
  });
});

// Cypress with Percy
describe('Visual Tests', () => {
  it('looks correct', () => {
    cy.visit('/component');
    cy.percySnapshot('Component State');
  });
});

CSS Unit Testing

// Testing CSS Modules
import styles from './Button.module.css';

test('Button styles', () => {
  expect(styles.primary).toBeDefined();
  expect(document.querySelector(`.${styles.primary}`))
    .toHaveStyle({
      backgroundColor: 'blue',
      color: 'white'
    });
});

Theme Switching

/* Theme token system */
:root {
  color-scheme: light dark;
  
  /* Base tokens */
  --color-primary-light: oklch(70% 0.2 250);
  --color-primary-dark: oklch(60% 0.2 250);
  
  /* Semantic tokens */
  --color-primary: var(--color-primary-light);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-primary: var(--color-primary-dark);
  }
}

/* Theme class override */
:root[data-theme="dark"] {
  --color-primary: var(--color-primary-dark);
}

Component Variants

/* Base component */
.button {
  --button-bg: var(--color-primary);
  --button-color: white;
  
  background: var(--button-bg);
  color: var(--button-color);
}

/* Variant system */
.button[data-variant="outline"] {
  --button-bg: transparent;
  --button-color: var(--color-primary);
  border: 2px solid currentColor;
}

/* Size variants */
.button[data-size="small"] {
  --button-padding: 0.5rem 1rem;
  --button-font: var(--font-size-small);
}

Style Isolation

/* Shadow DOM isolation */
class IsolatedComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          contain: content;
        }
        /* Scoped styles */
      </style>
      <div class="container">...</div>
    `;
  }
}

/* CSS Module federation */
const remoteFederatedStyles = await import('remote/styles');
document.adoptedStyleSheets = [
  ...document.adoptedStyleSheets,
  remoteFederatedStyles.default
];

Shared Style Management

/* Custom properties contract */
:root {
  /* Public API */
  --mfe-spacing-unit: 0.25rem;
  --mfe-primary-color: oklch(67% 0.2 230);
  --mfe-font-family: system-ui;
}

/* Container styles */
.mfe-container {
  /* Private namespace */
  --_internal-spacing: calc(var(--mfe-spacing-unit) * 4);
  padding: var(--_internal-spacing);
}

CSS DevTools Integration

/* Debug layout */
* {
  outline: 1px solid rgba(255, 0, 0, 0.2);
}

/* CSS custom property debugging */
.component {
  --debug-layout: true;
  
  @supports (transform: debug(true)) {
    transform: debug(var(--debug-layout));
  }
}

Source Maps Configuration

// webpack.config.js
module.exports = {
  module: {
    rules: [{
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            sourceMap: true,
            modules: {
              localIdentName: '[name]__[local]--[hash:base64:5]'
            }
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            sourceMap: true
          }
        }
      ]
    }]
  }
};

ARIA Integration

/* Enhanced focus styles */
*:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 2px;
}

/* High contrast support */
@media (forced-colors: active) {
  .button {
    border: 1px solid currentColor;
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Screen reader only content */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

Color Contrast Management

:root {
  --color-text: oklch(25% 0 0);
  --color-background: oklch(98% 0 0);
}

@media (prefers-contrast: more) {
  :root {
    --color-text: black;
    --color-background: white;
  }
}

Logical Properties

.container {
  /* Instead of left/right */
  margin-inline-start: 1rem;
  margin-inline-end: 1rem;
  
  /* Instead of top/bottom */
  padding-block-start: 1rem;
  padding-block-end: 1rem;
  
  /* Logical dimensions */
  inline-size: 100%;
  block-size: auto;
}

RTL Support

/* Base layout */
.layout {
  display: flex;
  flex-direction: row;
}

/* RTL support */
[dir="rtl"] .layout {
  flex-direction: row-reverse;
}

/* Writing modes */
.content {
  writing-mode: horizontal-tb;
  text-orientation: mixed;
}

Custom Property Animations

@property --slide {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: false;
}

.slider {
  --slide: 0%;
  transform: translateX(var(--slide));
  transition: --slide 0.3s ease;
}

.slider:hover {
  --slide: 100%;
}

Scroll-Driven Animations

@keyframes parallax {
  from { transform: translateZ(0); }
  to { transform: translateZ(-500px); }
}

.parallax {
  animation: parallax linear;
  animation-timeline: scroll();
  animation-range: entry 20% cover 80%;
}

Web Animations API Integration

element.animate(
  [
    { transform: 'scale(1)', opacity: '1' },
    { transform: 'scale(1.5)', opacity: '0' }
  ],
  {
    duration: 300,
    easing: 'ease-out',
    fill: 'forwards'
  }
);

CSS Injection Prevention

/* Sanitize user input */
.user-content {
  /* Restrict dangerous properties */
  all: initial;
  /* Allow safe properties */
  color: var(--safe-color);
  font-family: var(--safe-font);
}

Feature Detection

/* Progressive enhancement */
@supports (display: grid) {
  .modern-layout {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  }
}

/* Fallback first */
.layout {
  display: flex;
  flex-wrap: wrap;
}

@supports (display: grid) {
  .layout {
    display: grid;
  }
}

Performance Tracking

// Layout shift monitoring
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Layout shift:', entry.value, entry.sources);
  }
}).observe({ entryTypes: ['layout-shift'] });

// Paint timing
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.startTime}ms`);
  }
}).observe({ entryTypes: ['paint'] });

CSS Coverage Analysis

// Coverage API usage
async function analyzeCoverage() {
  const coverage = await CSS.startStyleCoverage();
  // Interact with page
  const result = await CSS.stopStyleCoverage();
  
  const unusedBytes = result.reduce((total, style) => {
    return total + style.unusedBytes;
  }, 0);
  
  console.log(`Unused CSS: ${unusedBytes} bytes`);
}

Component Documentation

/**
 * @component Button
 * @description Primary button component with variants
 * @example
 * <button class="button button--primary">
 *   Click me
 * </button>
 *
 * @cssvar --button-background - Button background color
 * @cssvar --button-color - Button text color
 */
.button {
  --button-background: var(--color-primary);
  --button-color: white;
  
  background: var(--button-background);
  color: var(--button-color);
}

Design Token Documentation

/**
 * @tokens Colors
 * @description Brand color palette
 */
:root {
  --color-primary-50: oklch(98% 0.02 250);
  --color-primary-100: oklch(95% 0.05 250);
  /* ... more color tokens */
}

/**
 * @tokens Spacing
 * @description Spacing scale
 */
:root {
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  /* ... more spacing tokens */
}

Style Guide Integration

/* Living style guide examples */
:root {
  /* Typography */
  --font-heading: system-ui;
  --font-body: sans-serif;
  
  /* Colors */
  --brand-primary: oklch(50% 0.2 250);
  --brand-secondary: oklch(60% 0.2 200);
  
  /* Spacing */
  --spacing-unit: 0.25rem;
  --spacing-scale: 1.5;
}

/* Usage documentation */
.component {
  font-family: var(--font-heading);
  color: var(--brand-primary);
  margin: calc(var(--spacing-unit) * 4);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment