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.
- Core Methodologies
- Modern Module Systems
- Modern Approaches and Tools
- Best Practices
- Modern Layout Patterns
- Performance and Optimization
- Methodology Comparison
- Modern CSS Features and Patterns
- Testing and Quality Assurance
- Design Systems Integration
- Micro-Frontend Architecture
- Modern Development Workflows
- Accessibility Implementation
- Internationalization
- Advanced Animation Patterns
- Security and Error Handling
- Monitoring and Analytics
- Documentation Standards
/* 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">
/* 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">
/* 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">
/* 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.
.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">
// styles.css
@import "./other-styles.css";
@layer components {
.button { /* ... */ }
}
// main.js
import './styles.css';
// Button.css
.button {
background: blue;
}
// Button.js
import styles from './Button.css' assert { type: 'css' };
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
// 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;
}
// style.module.css
export const button = 'btn_xyz123';
// Component.js
import { button } from './style.module.css';
element.classList.add(button);
// 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
};
// Dynamic import based on condition
if (condition) {
import('./large-styles.css')
.then(styles => {
// Apply styles
});
}
/* automatically scoped by bundler */
.title {
color: blue;
}
/* compiles to */
.title_xyz123 {
color: blue;
}
.base { /* ... */ }
.composed {
composes: base;
/* additional styles */
}
:global(.title) {
/* not scoped */
}
/* Component.module.css */
.title {
color: blue;
}
/* Compiles to: */
.title_ax7yz9 {
color: blue;
}
Effect: Automatically scopes CSS to components, preventing naming conflicts.
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.
<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.
:root {
--brand-color: #3490dc;
}
.button {
background: var(--brand-color);
}
Effect: Enables dynamic theming and value changes.
.js-* /* JavaScript hooks */
.c-* /* Components */
.u-* /* Utilities */
.t-* /* Themes */
- Single Responsibility Principle
- Open/Closed Principle
- DRY (Don't Repeat Yourself)
- Separation of Concerns
@layer base, components, utilities;
@layer base {
/* base styles */
}
@layer components {
/* component styles */
}
@container sidebar (min-width: 400px) {
.component {
/* responsive to container */
}
}
@layer framework {
/* third-party styles */
}
@layer theme {
/* theme overrides */
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.flex-container {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
/* Mobile First */
.element {
width: 100%;
}
@media (min-width: 768px) {
.element {
width: 50%;
}
}
/* Container Queries */
@container (min-width: 400px) {
.element {
/* Styles based on parent container */
}
}
// 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);
// Base styles
import './base.css';
// Feature detection and enhancement
if ('containQueries' in CSS) {
import('./container-queries.css');
}
// 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);
}
// vite.config.js
export default {
css: {
modules: {
scopeBehaviour: 'local',
localsConvention: 'camelCase',
generateScopedName: '[name]_[local]_[hash:base64:5]'
}
}
}
// 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;
};
- BEM: Naming and scope problems
- OOCSS: Reusability problems
- SMACSS: Organization problems
- ITCSS: Specificity problems
- Atomic: Maintenance and scaling problems
- 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
- Specificity management
- Critical CSS loading
- CSS selector performance
- File size optimization
/* 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;
}
@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%;
}
}
/* 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 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);
}
/* 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;
}
/* 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;
}
}
/* 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">
// 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');
});
});
// 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 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);
}
/* 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);
}
/* 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
];
/* 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);
}
/* 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));
}
}
// 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
}
}
]
}]
}
};
/* 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;
}
:root {
--color-text: oklch(25% 0 0);
--color-background: oklch(98% 0 0);
}
@media (prefers-contrast: more) {
:root {
--color-text: black;
--color-background: white;
}
}
.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;
}
/* 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;
}
@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%;
}
@keyframes parallax {
from { transform: translateZ(0); }
to { transform: translateZ(-500px); }
}
.parallax {
animation: parallax linear;
animation-timeline: scroll();
animation-range: entry 20% cover 80%;
}
element.animate(
[
{ transform: 'scale(1)', opacity: '1' },
{ transform: 'scale(1.5)', opacity: '0' }
],
{
duration: 300,
easing: 'ease-out',
fill: 'forwards'
}
);
/* Sanitize user input */
.user-content {
/* Restrict dangerous properties */
all: initial;
/* Allow safe properties */
color: var(--safe-color);
font-family: var(--safe-font);
}
/* 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;
}
}
// 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'] });
// 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 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);
}
/**
* @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 */
}
/* 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);
}