Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Avoid embedded and inline CSS

Embedded and inline CSS are avoided except for critical CSS and performance optimization.

Utilities
Quick take
Typical fix time 15 min
  • Move inline styles to external CSS files or CSS-in-JS
  • Exception: critical CSS should be inlined for performance
  • Exception: dynamic values (CSS custom properties) can be inline
  • Inline CSS breaks caching and increases HTML size
Why it matters: Inline CSS cannot be cached separately, increases HTML payload, breaks separation of concerns, and is harder to maintain in large codebases.

Rule Details

Avoid embedded CSS in <style> tags and inline CSS except for valid cases like critical CSS, dynamic styling, or performance optimization.

Code Examples

Problems with Inline CSS

<!-- ❌ Bad: Inline styles -->
<div style="background-color: red; padding: 20px; margin: 10px; font-size: 16px;">
  Content with inline styles
</div>
 
<p style="color: blue; text-align: center; font-weight: bold;">
  Another element with inline styles
</p>
 
<!-- ❌ Bad: Embedded styles -->
<style>
  .header { background: blue; padding: 10px; }
  .content { margin: 20px; font-size: 14px; }
  .footer { background: gray; padding: 15px; }
</style>

Problems This Creates

<!-- Issues with inline/embedded CSS: -->
 
<!-- 1. No caching - styles downloaded with every page -->
<!-- 2. Maintenance nightmare - styles scattered throughout HTML -->
<!-- 3. Specificity issues - inline styles have highest specificity -->
<!-- 4. No reusability - same styles repeated multiple times -->
<!-- 5. Hard to debug - styles mixed with markup -->
<!-- 6. Performance impact - larger HTML files -->

Why It Matters

Inline CSS cannot be cached separately, increases HTML payload, breaks separation of concerns, and is harder to maintain in large codebases.

Correct External CSS Approach

Proper CSS Structure

<!-- ✅ Good: External CSS -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Page Title</title>
 
  <!-- External CSS files -->
  <link rel="stylesheet" href="css/normalize.css">
  <link rel="stylesheet" href="css/base.css">
  <link rel="stylesheet" href="css/components.css">
  <link rel="stylesheet" href="css/layout.css">
</head>
<body>
  <div class="header">
    <h1 class="header__title">Page Title</h1>
  </div>
 
  <main class="content">
    <p class="content__text">Content with external CSS classes</p>
  </main>
 
  <footer class="footer">
    <p class="footer__text">Footer content</p>
  </footer>
</body>
</html>

External CSS Files

/* css/base.css */
html {
  font-size: 16px;
  line-height: 1.5;
}
 
body {
  margin: 0;
  font-family: 'Helvetica Neue', Arial, sans-serif;
  color: #333;
}
 
/* css/components.css */
.header {
  background: #007bff;
  padding: 1rem;
  color: white;
}
 
.header__title {
  margin: 0;
  font-size: 1.5rem;
}
 
.content {
  margin: 2rem;
  max-width: 800px;
}
 
.content__text {
  margin-bottom: 1rem;
  line-height: 1.6;
}
 
.footer {
  background: #6c757d;
  padding: 1rem;
  color: white;
  text-align: center;
}
 
.footer__text {
  margin: 0;
  font-size: 0.9rem;
}

Valid Use Cases for Inline/Embedded CSS

1. Critical Above-the-Fold CSS

<!-- ✅ Valid: Critical CSS for performance -->
<head>
  <style>
    /* Critical CSS - above-the-fold styles only */
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      margin: 0;
      line-height: 1.5;
    }
 
    .hero {
      height: 100vh;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
    }
 
    .hero__title {
      font-size: 3rem;
      text-align: center;
      margin: 0;
    }
  </style>
 
  <!-- Non-critical CSS loaded asynchronously -->
  <link rel="preload" href="css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="css/main.css"></noscript>
</head>

2. Dynamic Styling Based on Data

// ✅ Valid: React dynamic styles
function ProgressBar({ percentage, color }) {
  const dynamicStyles = {
    width: `${percentage}%`,
    backgroundColor: color,
    transition: 'width 0.3s ease'
  }
 
  return (
    <div className="progress-bar">
      <div
        className="progress-bar__fill"
        style={dynamicStyles}
        aria-valuenow={percentage}
        role="progressbar"
      />
    </div>
  )
}
 
// External CSS for structure
.progress-bar {
  width: 100%;
  height: 20px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
}
 
.progress-bar__fill {
  height: 100%;
  border-radius: 10px;
}

3. CSS-in-JS for Component Styling

// ✅ Valid: CSS-in-JS with proper abstraction
import styled from 'styled-components'
 
const Button = styled.button`
  background: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.2s ease;
 
  &:hover {
    background: ${props => props.primary ? '#0056b3' : '#545b62'};
  }
 
  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
`
 
// Usage
function App() {
  return (
    <div>
      <Button primary>Primary Button</Button>
      <Button>Secondary Button</Button>
    </div>
  )
}

4. User Customization

<!-- ✅ Valid: User-customizable styles -->
<div
  class="user-widget"
  style="--user-theme-color: {{ user.themeColor }}; --user-font-size: {{ user.fontSize }}px;"
>
  <h3 class="widget__title">User Dashboard</h3>
  <p class="widget__content">Customized content</p>
</div>
/* External CSS using custom properties */
.user-widget {
  background: var(--user-theme-color, #007bff);
  font-size: var(--user-font-size, 16px);
  padding: 1rem;
  border-radius: 8px;
  color: white;
}
 
.widget__title {
  margin: 0 0 0.5rem 0;
  font-size: calc(var(--user-font-size, 16px) * 1.25);
}
 
.widget__content {
  margin: 0;
  line-height: 1.5;
}

Framework-Specific Best Practices

React with CSS Modules

// ✅ React with CSS Modules
import styles from './Button.module.css'
 
function Button({ variant = 'primary', size = 'medium', children, ...props }) {
  const classNames = [
    styles.button,
    styles[`button--${variant}`],
    styles[`button--${size}`]
  ].join(' ')
 
  return (
    <button className={classNames} {...props}>
      {children}
    </button>
  )
}
 
// Button.module.css
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.2s ease;
  font-family: inherit;
}
 
.button--primary {
  background: #007bff;
  color: white;
}
 
.button--primary:hover {
  background: #0056b3;
}
 
.button--secondary {
  background: #6c757d;
  color: white;
}
 
.button--small {
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
}
 
.button--large {
  padding: 1rem 2rem;
  font-size: 1.125rem;
}

Vue with Scoped CSS

<!-- ✅ Vue with scoped CSS -->
<template>
  <div class="card" :class="cardClasses">
    <header class="card__header" v-if="title">
      <h3 class="card__title">{{ title }}</h3>
    </header>
 
    <div class="card__content">
      <slot />
    </div>
 
    <footer class="card__footer" v-if="hasFooter">
      <slot name="footer" />
    </footer>
  </div>
</template>
 
<script>
export default {
  props: {
    title: String,
    variant: {
      type: String,
      default: 'default',
      validator: value => ['default', 'primary', 'success', 'warning', 'danger'].includes(value)
    },
    elevated: Boolean
  },
 
  computed: {
    cardClasses() {
      return {
        [`card--${this.variant}`]: this.variant !== 'default',
        'card--elevated': this.elevated
      }
    },
 
    hasFooter() {
      return this.$slots.footer
    }
  }
}
</script>
 
<style scoped>
.card {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
  background: white;
  transition: box-shadow 0.2s ease;
}
 
.card--elevated {
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
 
.card--primary {
  border-color: #007bff;
}
 
.card--primary .card__header {
  background: #007bff;
  color: white;
}
 
.card__header {
  padding: 1rem;
  background: #f8f9fa;
  border-bottom: 1px solid #e0e0e0;
}
 
.card__title {
  margin: 0;
  font-size: 1.125rem;
}
 
.card__content {
  padding: 1rem;
}
 
.card__footer {
  padding: 0.75rem 1rem;
  background: #f8f9fa;
  border-top: 1px solid #e0e0e0;
}
</style>

Angular with Component Styles

// ✅ Angular with component styles
import { Component, Input } from '@angular/core'
 
@Component({
  selector: 'app-alert',
  template: `
    <div
      class="alert"
      [class]="'alert--' + type"
      [class.alert--dismissible]="dismissible"
      role="alert"
    >
      <div class="alert__icon" *ngIf="showIcon">
        <ng-content select="[slot=icon]"></ng-content>
      </div>
 
      <div class="alert__content">
        <h4 class="alert__title" *ngIf="title">{{ title }}</h4>
        <ng-content></ng-content>
      </div>
 
      <button
        class="alert__close"
        *ngIf="dismissible"
        (click)="onDismiss()"
        aria-label="Close"
      >
        ×
      </button>
    </div>
  `,
  styles: [`
    .alert {
      padding: 1rem;
      border-radius: 4px;
      border: 1px solid transparent;
      display: flex;
      align-items: flex-start;
      gap: 0.75rem;
    }
 
    .alert--success {
      background-color: #d4edda;
      border-color: #c3e6cb;
      color: #155724;
    }
 
    .alert--warning {
      background-color: #fff3cd;
      border-color: #ffeaa7;
      color: #856404;
    }
 
    .alert--danger {
      background-color: #f8d7da;
      border-color: #f5c6cb;
      color: #721c24;
    }
 
    .alert__content {
      flex: 1;
    }
 
    .alert__title {
      margin: 0 0 0.5rem 0;
      font-size: 1.125rem;
    }
 
    .alert__close {
      background: none;
      border: none;
      font-size: 1.5rem;
      cursor: pointer;
      opacity: 0.7;
    }
 
    .alert__close:hover {
      opacity: 1;
    }
  `]
})
export class AlertComponent {
  @Input() type: 'success' | 'warning' | 'danger' = 'success'
  @Input() title?: string
  @Input() dismissible = false
  @Input() showIcon = true
 
  onDismiss() {
    // Emit dismiss event
  }
}

Linting and Automated Detection

ESLint Rules for React

// .eslintrc.js
module.exports = {
  extends: [
    'plugin:react/recommended'
  ],
  rules: {
    // Warn about inline styles
    'react/forbid-component-props': ['warn', {
      forbid: [
        {
          propName: 'style',
          message: 'Avoid inline styles. Use CSS classes or CSS-in-JS instead.'
        }
      ]
    }],
 
    // Warn about DOM attributes with style
    'react/forbid-dom-props': ['warn', {
      forbid: [
        {
          propName: 'style',
          message: 'Avoid inline styles. Use CSS classes instead.'
        }
      ]
    }]
  }
}

Stylelint Configuration

// .stylelintrc.js
module.exports = {
  extends: ['stylelint-config-standard'],
  rules: {
    // Disallow empty sources
    'no-empty-source': true,
 
    // Require specific patterns for class names
    'selector-class-pattern': '^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z0-9]+(-[a-z0-9]+)*)?(--[a-z0-9]+(-[a-z0-9]+)*)?$',
 
    // Limit nesting depth
    'max-nesting-depth': 3,
 
    // Disallow unknown properties
    'property-no-unknown': true,
 
    // Require specific units for font sizes
    'unit-allowed-list': ['px', 'rem', 'em', '%', 'vw', 'vh', 'deg', 's', 'ms']
  }
}

PostCSS Plugin for Inline Style Detection

// postcss-no-inline-styles.js
const postcss = require('postcss')
 
module.exports = postcss.plugin('postcss-no-inline-styles', () => {
  return (root, result) => {
    // This plugin would analyze HTML and warn about inline styles
    // Implementation would scan for style attributes in HTML templates
 
    root.walkRules(rule => {
      if (rule.selector.includes('[style]')) {
        result.warn('Avoid targeting elements with inline styles', {
          node: rule
        })
      }
    })
  }
})

Performance Optimization Strategies

Critical CSS Extraction

// critical-css-webpack-plugin.js
const critical = require('critical')
const HtmlWebpackPlugin = require('html-webpack-plugin')
 
class CriticalCSSPlugin {
  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync('CriticalCSSPlugin', async (compilation, callback) => {
      try {
        const htmlFiles = Object.keys(compilation.assets)
          .filter(name => name.endsWith('.html'))
 
        for (const htmlFile of htmlFiles) {
          await critical.generate({
            src: `dist/${htmlFile}`,
            dest: `dist/${htmlFile}`,
            inline: true,
            minify: true,
            width: 1300,
            height: 900,
            penthouse: {
              blockJSRequests: false
            }
          })
        }
 
        callback()
      } catch (error) {
        callback(error)
      }
    })
  }
}
 
// Usage in webpack.config.js
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
 
    new CriticalCSSPlugin()
  ]
}

CSS-in-JS Performance Best Practices

// ✅ Optimized CSS-in-JS patterns
import { memo, useMemo } from 'react'
import styled, { css } from 'styled-components'
 
// 1. Memoize dynamic styles
const Button = memo(styled.button`
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s ease;
 
  ${props => props.variant === 'primary' && css`
    background: #007bff;
    color: white;
 
    &:hover {
      background: #0056b3;
    }
  `}
 
  ${props => props.variant === 'secondary' && css`
    background: #6c757d;
    color: white;
 
    &:hover {
      background: #545b62;
    }
  `}
`)
 
// 2. Extract static styles
const staticButtonStyles = css`
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s ease;
`
 
// 3. Use CSS custom properties for dynamic values
const ThemeProvider = ({ theme, children }) => {
  const themeStyles = useMemo(() => ({
    '--primary-color': theme.primary,
    '--secondary-color': theme.secondary,
    '--font-size-base': theme.fontSize + 'px'
  }), [theme])
 
  return (
    <div style={themeStyles} className="theme-provider">
      {children}
    </div>
  )
}

Best Practices Summary

  1. External CSS First: Use external stylesheets as the primary styling method
  2. Critical CSS Exception: Inline only critical above-the-fold CSS
  3. Dynamic Styles: Use inline styles only for truly dynamic values
  4. Component Architecture: Use CSS Modules, styled-components, or scoped CSS
  5. Performance: Minimize inline styles to reduce HTML size
  6. Maintainability: Keep styles organized and reusable
  7. Caching: External CSS benefits from browser caching
  8. Separation of Concerns: Keep styling separate from markup
  9. Linting: Use tools to detect and prevent inline style usage
  10. Documentation: Document exceptions and rationale for inline CSS usage

Migration Strategy

Converting Inline to External CSS

// inline-to-external-converter.js
const fs = require('fs')
const cheerio = require('cheerio')
 
class InlineToExternalConverter {
  constructor() {
    this.extractedStyles = new Map()
    this.classCounter = 0
  }
 
  convertFile(filePath) {
    const content = fs.readFileSync(filePath, 'utf8')
    const $ = cheerio.load(content)
 
    $('[style]').each((index, element) => {
      const $element = $(element)
      const inlineStyles = $element.attr('style')
 
      // Generate unique class name
      const className = `extracted-${this.classCounter++}`
 
      // Store extracted styles
      this.extractedStyles.set(className, inlineStyles)
 
      // Replace inline style with class
      $element.removeClass().addClass(className)
      $element.removeAttr('style')
    })
 
    // Write updated HTML
    fs.writeFileSync(filePath, $.html())
 
    return this.generateCSS()
  }
 
  generateCSS() {
    let css = '/* Extracted inline styles */\n\n'
 
    for (const [className, styles] of this.extractedStyles) {
      css += `.${className} {\n`
      css += `  ${styles.replace(/;/g, ';\n  ')}\n`
      css += '}\n\n'
    }
 
    return css
  }
}
 
// Usage
const converter = new InlineToExternalConverter()
const extractedCSS = converter.convertFile('index.html')
fs.writeFileSync('extracted-styles.css', extractedCSS)

Verification

Automated Checks

  • Confirm the computed styles match the intended fix in DevTools.
  • If the rule affects motion, contrast, or layout stability, verify those user-facing outcomes directly.

Manual Checks

  • Inspect the rendered UI at the breakpoints and interaction states affected by the rule.
  • Test at least one mobile and one desktop viewport before shipping.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify that inline CSS and embedded styles are avoided except for valid use cases like critical CSS, dynamic styles, or performance optimization.

Fix

Auto-fix issues

Move inline and embedded CSS to external stylesheets, keeping only critical above-the-fold CSS inline for performance optimization.

Explain

Learn more

Explain why external CSS is preferred for maintainability, caching, and separation of concerns, with exceptions for critical CSS and dynamic styles.

Review

Code review

Review stylesheets, component styles, and responsive states related to Avoid embedded and inline CSS. Flag exact selectors, declarations, or breakpoints that violate the rule in the rendered UI.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Chrome DevTools
developer.chrome.comTool

Rules that often go hand-in-hand with this one.

Include a print stylesheet

A print stylesheet is provided and correctly optimized for printed pages.

CSS
Keep CSS specificity low and flat

Write selectors at the lowest specificity that works, avoiding ID selectors and deep nesting, so styles can be overridden cleanly without resorting to !important.

CSS
Avoid inline JavaScript

Inline JavaScript is avoided. JavaScript is kept in external files for caching and maintainability.

JavaScript
Use @layer to manage CSS cascade order explicitly

CSS Cascade Layers (@layer) are used to give the codebase explicit, predictable control over specificity and cascade order, eliminating the need to fight specificity with !important.

CSS

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385