# Inline CSS info and size

> Source: <https://webperf-snippets.net/Loading/Inline-CSS-Info-and-Size>

Analyzes all inline `<style>` tags on the page, measuring their size and identifying optimization opportunities. This helps understand how much CSS is inlined and whether it's being used effectively.

### When inline CSS is good

| Scenario                   | Benefit                                              |
| -------------------------- | ---------------------------------------------------- |
| **Critical CSS** (< 14 KB) | Eliminates render-blocking request, faster FCP       |
| **Above-the-fold styles**  | Immediate rendering without waiting for external CSS |
| **Single-page apps**       | Reduces HTTP requests for small style chunks         |

### When inline CSS is problematic

| Issue            | Impact                           | Threshold                              |
| ---------------- | -------------------------------- | -------------------------------------- |
| **Too large**    | Bloats HTML, slower TTFB         | > 14 KB (TCP slow start)               |
| **Duplicated**   | Wasted bytes, harder maintenance | Same styles in multiple `<style>` tags |
| **Not critical** | Delays HTML parsing              | Styles for below-the-fold content      |
| **Uncached**     | Re-downloaded on every page      | Large styles that could be external    |

> **Note:** **14 KB Rule:** The first TCP round-trip can deliver \~14 KB. Critical CSS should fit within this budget for optimal First Contentful Paint.

### Snippet

```js
// Inline CSS Analysis - Find and analyze all inline style tags
// https://webperf-snippets.net

(() => {
  // @shared formatBytes
  function formatBytes(bytes) {
    if (bytes === null || bytes === undefined || Number.isNaN(bytes)) return "-";
    if (bytes === 0) return "0 B";
    const units = ["B", "KB", "MB", "GB"];
    const i = Math.max(0, Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1));
    return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
  }
  // @end-shared formatBytes

  const formatKB = (bytes) => (bytes / 1024).toFixed(2);

  // Get all inline style tags
  const styleTags = Array.from(document.querySelectorAll("style"));

  if (styleTags.length === 0) {
    console.group("%c📝 Inline CSS Analysis", "font-weight: bold; font-size: 14px;");
    console.log("%c✅ No inline <style> tags found.", "color: #22c55e; font-weight: bold;");
    console.log("The page uses external stylesheets only.");
    console.groupEnd();
    return { script: "Inline-CSS-Info-and-Size", status: "ok", count: 0, items: [], issues: [] };
  }

  // Analyze each style tag
  const styles = styleTags.map((style, index) => {
    const content = style.innerHTML;
    const size = new Blob([content]).size;
    const parent = style.parentElement?.tagName.toLowerCase() || "unknown";
    const inHead = style.closest("head") !== null;

    // Count approximate number of rules
    const ruleCount = (content.match(/\{[^}]*\}/g) || []).length;

    // Get preview (first 100 chars, cleaned up)
    const preview = content
      .replace(/\s+/g, " ")
      .trim()
      .slice(0, 80);

    // Check for media queries
    const hasMediaQueries = /@media/i.test(content);

    // Check for keyframes (animation)
    const hasKeyframes = /@keyframes/i.test(content);

    return {
      index: index + 1,
      element: style,
      size,
      sizeKB: formatKB(size),
      parent,
      inHead,
      ruleCount,
      preview: preview + (content.length > 80 ? "..." : ""),
      hasMediaQueries,
      hasKeyframes,
      content,
    };
  });

  // Calculate totals and find issues
  const totalSize = styles.reduce((sum, s) => sum + s.size, 0);
  const totalRules = styles.reduce((sum, s) => sum + s.ruleCount, 0);
  const criticalBudget = 14 * 1024; // 14 KB

  // Find duplicated content
  const contentMap = new Map();
  styles.forEach((s) => {
    const normalized = s.content.replace(/\s+/g, " ").trim();
    if (!contentMap.has(normalized)) {
      contentMap.set(normalized, []);
    }
    contentMap.get(normalized).push(s.index);
  });
  const duplicates = Array.from(contentMap.entries())
    .filter(([, indices]) => indices.length > 1)
    .map(([, indices]) => indices);

  // Rating based on total size
  let rating, ratingColor;
  if (totalSize <= criticalBudget * 0.5) {
    rating = "Good";
    ratingColor = "#22c55e";
  } else if (totalSize <= criticalBudget) {
    rating = "Acceptable";
    ratingColor = "#f59e0b";
  } else {
    rating = "Too Large";
    ratingColor = "#ef4444";
  }

  // Display results
  console.group("%c📝 Inline CSS Analysis", "font-weight: bold; font-size: 14px;");

  // Summary
  console.log("");
  console.log("%cSummary:", "font-weight: bold;");
  console.log(`   Total inline <style> tags: ${styles.length}`);
  console.log(`   Total size: ${formatBytes(totalSize)} (${formatKB(totalSize)} KB)`);
  console.log(`   Total CSS rules: ~${totalRules}`);
  console.log(
    `   Budget used: ${((totalSize / criticalBudget) * 100).toFixed(0)}% of 14 KB`
  );
  console.log(`   Rating: %c${rating}`, `color: ${ratingColor}; font-weight: bold;`);

  // Visual budget bar
  const budgetPct = Math.min((totalSize / criticalBudget) * 100, 100);
  const barWidth = Math.round(budgetPct / 5);
  const bar = "█".repeat(barWidth) + "░".repeat(20 - barWidth);
  console.log(`   [${bar}] ${budgetPct.toFixed(0)}%`);

  // Details table
  console.log("");
  console.group(
    `%c📊 Style Tags (${styles.length})`,
    "color: #3b82f6; font-weight: bold;"
  );

  const tableData = styles.map((s) => ({
    "#": s.index,
    Location: s.inHead ? "head" : "body",
    Size: formatBytes(s.size),
    Rules: s.ruleCount,
    "Media Queries": s.hasMediaQueries ? "Yes" : "-",
    Animations: s.hasKeyframes ? "Yes" : "-",
    Preview: s.preview,
  }));
  console.table(tableData);

  // Elements for inspection
  console.log("");
  console.log("%c🔎 Elements for inspection:", "font-weight: bold;");
  styles.forEach((s) => {
    const warning = s.size > 5 * 1024 ? " ⚠️ Large" : "";
    console.log(`${s.index}. (${formatBytes(s.size)})${warning}`, s.element);
  });

  console.groupEnd();

  // Issues
  const issues = [];

  if (totalSize > criticalBudget) {
    issues.push({
      type: "size",
      message: `Total inline CSS (${formatBytes(totalSize)}) exceeds 14 KB critical budget`,
      suggestion: "Move non-critical styles to external stylesheet",
    });
  }

  if (duplicates.length > 0) {
    issues.push({
      type: "duplicate",
      message: `Found ${duplicates.length} duplicated style block(s)`,
      details: duplicates.map((d) => `Tags #${d.join(", #")} have identical content`),
      suggestion: "Consolidate duplicate styles into a single block",
    });
  }

  const largeBlocks = styles.filter((s) => s.size > 5 * 1024);
  if (largeBlocks.length > 0) {
    issues.push({
      type: "large",
      message: `${largeBlocks.length} style block(s) over 5 KB`,
      details: largeBlocks.map((s) => `Tag #${s.index}: ${formatBytes(s.size)}`),
      suggestion: "Consider moving large blocks to external cached stylesheets",
    });
  }

  const bodyStyles = styles.filter((s) => !s.inHead);
  if (bodyStyles.length > 0) {
    issues.push({
      type: "location",
      message: `${bodyStyles.length} style block(s) in <body>`,
      details: bodyStyles.map((s) => `Tag #${s.index}`),
      suggestion: "Move styles to <head> or use external stylesheets for better parsing",
    });
  }

  if (issues.length > 0) {
    console.log("");
    console.group("%c⚠️ Issues Found", "color: #ef4444; font-weight: bold;");

    issues.forEach((issue) => {
      console.log("");
      console.log(`%c${issue.message}`, "font-weight: bold;");
      if (issue.details) {
        issue.details.forEach((d) => console.log(`   • ${d}`));
      }
      console.log(`   → ${issue.suggestion}`);
    });

    console.groupEnd();
  } else {
    console.log("");
    console.log(
      "%c✅ No issues found. Inline CSS is within recommended limits.",
      "color: #22c55e; font-weight: bold;"
    );
  }

  // Recommendations
  console.log("");
  console.group("%c📝 Best Practices", "color: #3b82f6; font-weight: bold;");
  console.log("");
  console.log("• Keep critical CSS under 14 KB (fits in first TCP round-trip)");
  console.log("• Inline only above-the-fold critical styles");
  console.log("• Move large stylesheets to external files for caching");
  console.log("• Avoid duplicate style blocks");
  console.log("• Place <style> tags in <head> for optimal parsing");
  console.log("");
  console.log("%cExample critical CSS pattern:", "font-weight: bold;");
  console.log('%c<head>\n  <style>/* Critical above-fold CSS here */</style>\n  <link rel="preload" href="main.css" as="style">\n  <link rel="stylesheet" href="main.css" media="print" onload="this.media=\'all\'">\n</head>', "font-family: monospace; color: #22c55e;");
  console.groupEnd();

  console.groupEnd();

  return {
    script: "Inline-CSS-Info-and-Size",
    status: "ok",
    count: styles.length,
    details: {
      totalSizeBytes: totalSize,
      totalRules,
      criticalBudgetBytes: criticalBudget,
      duplicateBlocks: duplicates.length,
      bodyStyleBlocks: styles.filter(s => !s.inHead).length,
    },
    items: styles.map(s => ({ index: s.index, sizeBytes: s.size, parent: s.parent, inHead: s.inHead, ruleCount: s.ruleCount, hasMediaQueries: s.hasMediaQueries, hasKeyframes: s.hasKeyframes })),
    issues: issues.map(i => ({ severity: "warning", message: i.message })),
  };
})();
```

### Understanding the results

### Summary section

- Total inline style tags and combined size
- Approximate CSS rule count
- Percentage of 14 KB critical budget used
- Visual budget bar

### Details table

| Column        | Description                     |
| ------------- | ------------------------------- |
| #             | Style tag index                 |
| Location      | Whether in `<head>` or `<body>` |
| Size          | Byte size of the CSS content    |
| Rules         | Approximate number of CSS rules |
| Media Queries | Contains `@media` rules         |
| Animations    | Contains `@keyframes`           |
| Preview       | First 80 characters of CSS      |

### Issues detected

| Issue                 | Why It Matters                            |
| --------------------- | ----------------------------------------- |
| Total size > 14 KB    | Exceeds TCP slow start budget, delays FCP |
| Duplicate blocks      | Wasted bytes, maintenance burden          |
| Large blocks (> 5 KB) | Could benefit from external caching       |
| Styles in `<body>`    | May cause reflows during parsing          |

### The 14 KB rule

The first TCP round-trip delivers approximately 14 KB of data. For optimal performance:

```
┌─────────────────────────────────────────────┐
│  First 14 KB (one round-trip)               │
│  ┌───────────────────────────────────────┐  │
│  │ HTML + Critical CSS + Critical JS     │  │
│  │ → Enough to render above-the-fold     │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘
```

### Browser support

| Feature                                                                                     | Chrome | Edge | Firefox | Safari |
| ------------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :----: |
| [HTMLStyleElement.media](https://developer.mozilla.org/docs/Web/API/HTMLStyleElement/media) |   ✅ 1  | ✅ 12 |   ✅ 1   |   ✅ 1  |

Source: [MDN browser compatibility data](https://github.com/mdn/browser-compat-data), version 8.1.4.

### Further reading

- [Extract critical CSS](https://web.dev/articles/extract-critical-css) | web.dev
- [Defer non-critical CSS](https://web.dev/articles/defer-non-critical-css) | web.dev
- [Reduce the scope of style calculations](https://web.dev/articles/reduce-the-scope-and-complexity-of-style-calculations) | web.dev
- [Critical rendering path](https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path) | MDN
