Loading
CSS Media Queries Analysis

CSS Media Queries Analysis

Analyze all @media rules in CSS stylesheets to identify classes and properties targeting viewports bigger than a specified breakpoint (default: 768px). Results are grouped by inline CSS and external files, with byte size estimates for potential mobile savings.

Snippet

// Analyze CSS @media rules for viewports bigger than a specified breakpoint
// Default minWidth = 768 (px), but you can customize it
 
function analyzeCSSMediaQueries(minWidth = 768) {
  const stylesheets = [...document.styleSheets];
  const inlineMediaQueries = [];
  const fileMediaQueries = [];
  let inlineTotalClasses = 0;
  let inlineTotalProperties = 0;
  let filesTotalClasses = 0;
  let filesTotalProperties = 0;
  let inlineTotalBytes = 0;
  let filesTotalBytes = 0;
 
  // Helper to check if media query targets bigger than specified breakpoint
  function isBiggerThanBreakpoint(mediaText) {
    if (!mediaText) return false;
 
    // Check for min-width greater than specified breakpoint
    const minWidthMatch = mediaText.match(/min-width:\s*(\d+)(px|em|rem)/i);
    if (minWidthMatch) {
      const value = parseInt(minWidthMatch[1]);
      const unit = minWidthMatch[2].toLowerCase();
 
      if (unit === "px" && value > minWidth) return true;
      if (unit === "em" && value > minWidth / 16) return true; // Convert to em
      if (unit === "rem" && value > minWidth / 16) return true; // Convert to rem
    }
 
    // Check for max-width to exclude (mobile-only queries)
    const maxWidthMatch = mediaText.match(/max-width:\s*(\d+)(px|em|rem)/i);
    if (maxWidthMatch && !minWidthMatch) {
      return false; // max-width only queries are for smaller viewports
    }
 
    return false;
  }
 
  // Helper to count classes and properties in CSS text
  function countClassesAndProperties(cssText) {
    const classMatches = cssText.match(/\.[a-zA-Z0-9_-]+/g) || [];
    const propertyMatches = cssText.match(/[a-z-]+\s*:/g) || [];
 
    return {
      classes: classMatches.length,
      properties: propertyMatches.length,
    };
  }
 
  // Helper to calculate byte size
  function getByteSize(text) {
    return new Blob([text]).size;
  }
 
  // Helper to format bytes
  function formatBytes(bytes) {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }
 
  // Iterate through all stylesheets
  stylesheets.forEach((sheet, sheetIndex) => {
    try {
      const rules = sheet.cssRules || sheet.rules;
      if (!rules) return;
 
      const isInline = !sheet.href;
      const source = sheet.href || `<style> tag #${sheetIndex}`;
 
      [...rules].forEach((rule) => {
        // Check if it's a media rule
        if (rule.type === CSSRule.MEDIA_RULE) {
          const mediaText = rule.media.mediaText;
 
          if (isBiggerThanBreakpoint(mediaText)) {
            const cssText = rule.cssText;
            const counts = countClassesAndProperties(cssText);
            const byteSize = getByteSize(cssText);
 
            const mediaQueryData = {
              source: source,
              mediaQuery: mediaText,
              classes: counts.classes,
              properties: counts.properties,
              bytes: byteSize,
              bytesFormatted: formatBytes(byteSize),
            };
 
            if (isInline) {
              inlineMediaQueries.push(mediaQueryData);
              inlineTotalClasses += counts.classes;
              inlineTotalProperties += counts.properties;
              inlineTotalBytes += byteSize;
            } else {
              fileMediaQueries.push(mediaQueryData);
              filesTotalClasses += counts.classes;
              filesTotalProperties += counts.properties;
              filesTotalBytes += byteSize;
            }
          }
        }
      });
    } catch (e) {
      console.warn(`Cannot access stylesheet ${sheetIndex}:`, e.message);
    }
  });
 
  const totalBytes = inlineTotalBytes + filesTotalBytes;
 
  // Display results
  console.group(`📊 CSS Media Queries Analysis (min-width > ${minWidth}px)`);
  console.log(`Total @media rules found: ${inlineMediaQueries.length + fileMediaQueries.length}`);
  console.log(`Total classes: ${inlineTotalClasses + filesTotalClasses}`);
  console.log(`Total properties: ${inlineTotalProperties + filesTotalProperties}`);
  console.groupEnd();
 
  // Mobile Savings Estimate
  console.group("💾 POTENTIAL MOBILE SAVINGS");
  console.log(
    `%cEstimated CSS bytes that mobile doesn't need: ${formatBytes(totalBytes)}`,
    "font-weight: bold; color: #22c55e; font-size: 14px;",
  );
  console.log(`  └─ From inline CSS: ${formatBytes(inlineTotalBytes)}`);
  console.log(`  └─ From external files: ${formatBytes(filesTotalBytes)}`);
  console.log("");
  console.log("💡 By splitting these styles into separate files with media queries,");
  console.log("   mobile users won't need to download, parse, or process this CSS.");
  console.groupEnd();
 
  // Inline CSS Results
  console.group("🔷 INLINE CSS (<style> tags)");
  console.log(`Media queries: ${inlineMediaQueries.length}`);
  console.log(`Classes: ${inlineTotalClasses}`);
  console.log(`Properties: ${inlineTotalProperties}`);
  console.log(`Total size: ${formatBytes(inlineTotalBytes)}`);
  if (inlineMediaQueries.length > 0) {
    // console.table(inlineMediaQueries, [
    //   "source",
    //   "mediaQuery",
    //   "classes",
    //   "properties",
    //   "bytesFormatted",
    // ]);
  } else {
    console.log(`No inline media queries found for viewports > ${minWidth}px.`);
  }
  console.groupEnd();
 
  // External Files Results
  console.group("📁 EXTERNAL FILES (.css files)");
  console.log(`Media queries: ${fileMediaQueries.length}`);
  console.log(`Classes: ${filesTotalClasses}`);
  console.log(`Properties: ${filesTotalProperties}`);
  console.log(`Total size: ${formatBytes(filesTotalBytes)}`);
  if (fileMediaQueries.length > 0) {
    // console.table(fileMediaQueries, [
    //   "source",
    //   "mediaQuery",
    //   "classes",
    //   "properties",
    //   "bytesFormatted",
    // ]);
  } else {
    console.log(`No external file media queries found for viewports > ${minWidth}px.`);
  }
  console.groupEnd();
 
  return {
    summary: {
      total: {
        mediaQueries: inlineMediaQueries.length + fileMediaQueries.length,
        classes: inlineTotalClasses + filesTotalClasses,
        properties: inlineTotalProperties + filesTotalProperties,
        bytes: totalBytes,
        bytesFormatted: formatBytes(totalBytes),
      },
      inline: {
        mediaQueries: inlineMediaQueries.length,
        classes: inlineTotalClasses,
        properties: inlineTotalProperties,
        bytes: inlineTotalBytes,
        bytesFormatted: formatBytes(inlineTotalBytes),
      },
      files: {
        mediaQueries: fileMediaQueries.length,
        classes: filesTotalClasses,
        properties: filesTotalProperties,
        bytes: filesTotalBytes,
        bytesFormatted: formatBytes(filesTotalBytes),
      },
    },
    details: {
      inline: inlineMediaQueries,
      files: fileMediaQueries,
    },
  };
}
 
// Run with default breakpoint (768px)
analyzeCSSMediaQueries();
 
// Or customize the breakpoint:
// analyzeCSSMediaQueries(1024);  // for desktop
// analyzeCSSMediaQueries(480);   // for small tablets

Understanding the Results

The snippet analyzes and groups results by:

Potential Mobile Savings:

  • Estimated bytes that mobile devices don't need to download/process
  • Breakdown by inline CSS vs external files
  • This represents CSS that could be eliminated from mobile experience by splitting files

Inline CSS (<style> tags):

  • Number of @media rules targeting viewports larger than the specified breakpoint
  • Total CSS class selectors in inline media queries
  • Total CSS properties in inline media queries
  • Total byte size of inline media queries

External Files (.css files):

  • Number of @media rules targeting viewports larger than the specified breakpoint
  • Total CSS class selectors in external file media queries
  • Total CSS properties in external file media queries
  • Total byte size of external file media queries

Each media query includes:

  • source: Stylesheet URL or inline style tag identifier
  • mediaQuery: The media query condition (e.g., min-width: 1024px)
  • classes: Number of CSS class selectors
  • properties: Number of CSS properties
  • bytesFormatted: Size of the CSS in bytes (KB/MB)

Customizing the Breakpoint

By default, the snippet uses 768px as the mobile breakpoint, but you can customize it:

// Analyze media queries bigger than 1024px (desktop)
analyzeCSSMediaQueries(1024);
 
// Analyze media queries bigger than 480px (small tablets)
analyzeCSSMediaQueries(480);
 
// Analyze media queries bigger than 1440px (large desktop)
analyzeCSSMediaQueries(1440);

Breakpoint Detection

The snippet considers a media query "bigger than the breakpoint" when:

  • min-width is greater than the specified value (in px, em, or rem)
  • Excludes max-width only queries (typically for smaller viewports)
  • Automatically converts em/rem units (assuming 16px base font size)

Solutions & Optimization Strategies

Understanding how much CSS is dedicated to larger viewports helps identify optimization opportunities:

1. Split CSS by Media Query

  • Separate mobile and desktop CSS into different files
  • Load desktop CSS with a media query: <link media="(min-width: 768px)" href="desktop.css">
  • Reduces initial CSS parse time on mobile devices
  • Reduces style recalculation work (browser doesn't need to process desktop styles)

2. Inline Critical Above-the-Fold CSS

  • Move critical mobile CSS inline in <style> tags
  • Defer non-critical and desktop CSS loading
  • Improves First Contentful Paint (FCP) and Largest Contentful Paint (LCP) on mobile

3. Use CSS-in-JS or CSS Modules

  • Modern bundlers can split CSS by route/component
  • Only load CSS when needed (code splitting)
  • Reduces unused CSS on initial load

4. Consider Mobile-First Approach

  • Write base styles for mobile without media queries
  • Use min-width media queries for progressive enhancement
  • Reduces CSS overhead on mobile devices

5. Audit and Remove Unused Desktop CSS

  • If desktop media queries contain excessive classes/properties
  • Use tools like PurgeCSS or UnCSS to remove unused selectors
  • Reduces overall CSS bundle size

6. Measure the Impact

  • The "Potential Mobile Savings" metric shows exactly how many bytes mobile users could save
  • Compare this against your total CSS size to understand the optimization opportunity
  • Even 20-50 KB savings can improve mobile performance significantly

Further Reading

For an in-depth understanding of CSS and network performance optimization strategies, check out this excellent article:

📖 CSS and Network Performance (opens in a new tab) by Harry Roberts (opens in a new tab) (CSS Wizardry)

This article covers:

  • How CSS blocks rendering and impacts performance
  • Strategies for optimizing CSS delivery
  • Using media queries to conditionally load CSS
  • Measuring and improving CSS performance

Note

Cross-origin stylesheets cannot be accessed due to CORS restrictions. The snippet will warn about these and skip them.