Build a CSS Grid Inspector Extension

24 min read

Build a CSS Grid Inspector Extension

Build a CSS Grid Inspector Extension

CSS Grid has revolutionized web layout design, offering developers unprecedented control over two-dimensional layouts. However, debugging grid-based layouts can be challenging without the right tools. While browser DevTools provide some grid inspection capabilities, a dedicated CSS Grid Inspector extension offers enhanced functionality, custom overlays, and quick access directly from your browser toolbar. In this comprehensive guide, we will walk you through building a fully functional CSS Grid Inspector extension using Chrome’s Manifest V3 architecture.

Whether you are a web developer struggling with complex grid layouts or someone looking to learn Chrome extension development, this tutorial provides everything you need to create a powerful tool for debugging and visualizing CSS grids.


Why Build a CSS Grid Inspector Extension?

The ability to visualize and debug CSS Grid layouts is crucial for modern web development. While Chrome DevTools includes basic grid inspection features, a dedicated extension offers several compelling advantages that make building one worthwhile.

First and foremost, a custom grid overlay chrome extension provides instant visual feedback without navigating through multiple DevTools panels. You can toggle grid overlays with a single click, highlight specific grid areas, and customize overlay colors to match your preferences. This immediate access significantly speeds up the debugging workflow, especially when working on complex layouts that require frequent adjustments.

From a learning perspective, building a CSS Grid Inspector touches on many essential aspects of Chrome extension development. You will work with content scripts that interact directly with web pages, master the DOM API for detecting grid containers, implement visual overlays using CSS and JavaScript, and create popup interfaces for user interaction. This makes it an excellent project for developers looking to expand their extension development skills.

Additionally, the web development community constantly needs better CSS layout tools. Extension creators who build grid inspectors contribute valuable resources to the ecosystem while potentially solving their own pain points in everyday development work.


Understanding the Extension Architecture

Before writing any code, let us understand the core components that make up our CSS Grid Inspector extension. Chrome extensions built with Manifest V3 consist of several parts that work together to deliver functionality.

The Manifest File

The manifest.json file serves as the configuration hub for your extension. It defines the extension name, version, permissions, and declares which scripts and resources the extension will use. For our grid inspector, we will need permissions to access the active tab and execute scripts within page contexts.

Content Scripts

Content scripts are JavaScript files that run in the context of web pages. They can read and modify the DOM, access computed styles using getComputedStyle, and interact with page elements. This is where the core grid detection and overlay rendering logic will live.

The popup provides our user interface for toggling the inspector, configuring overlay options, and controlling which grid information to display. This creates the interactive experience users expect from a toolbar extension.

Background Service Worker

The background service worker handles extension lifecycle events and can coordinate between different parts of the extension. While our grid inspector might not heavily rely on this, understanding its role is important for building more complex extensions.


Project Setup and Directory Structure

Let us start by setting up our project structure. Create a new folder for your extension project with the following directory structure:

css-grid-inspector/
├── manifest.json
├── popup/
│   ├── popup.html
│   ├── popup.css
│   └── popup.js
├── content/
│   ├── content.js
│   └── content.css
├── background/
│   └── background.js
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

This structure follows Chrome extension best practices, keeping different components organized and separated by functionality. Now let us create each file, starting with the manifest.


Creating the Manifest File

The manifest.json file defines our extension configuration:

{
  "manifest_version": 3,
  "name": "CSS Grid Inspector",
  "version": "1.0",
  "description": "Inspect and visualize CSS Grid layouts with customizable overlays",
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content/content.js"],
      "css": ["content/content.css"],
      "run_at": "document_idle"
    }
  ],
  "background": {
    "service_worker": "background/background.js",
    "type": "module"
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

This manifest declares the necessary permissions for our extension. We use the scripting permission to execute content scripts and the activeTab permission to access the current tab information. The content_scripts section ensures our grid detection and overlay code runs on all web pages.


Building the Content Script

The content script is the heart of our extension. It detects grid containers, analyzes their grid properties, and renders visual overlays. Create content/content.js:

// Grid Inspector Content Script
(function() {
  'use strict';

  // State management
  let isEnabled = false;
  let overlaySettings = {
    showGridLines: true,
    showTrackSizes: true,
    showAreaNames: false,
    gapColor: '#ff00ff',
    lineColor: 'rgba(255, 0, 0, 0.5)',
    backgroundColor: 'rgba(0, 255, 255, 0.1)'
  };

  // Store for grid container references
  const gridOverlays = new WeakMap();

  // Listen for messages from popup
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'toggleInspector') {
      toggleInspector();
      sendResponse({ success: true, enabled: isEnabled });
    } else if (message.action === 'updateSettings') {
      overlaySettings = { ...overlaySettings, ...message.settings };
      if (isEnabled) {
        refreshOverlays();
      }
      sendResponse({ success: true });
    } else if (message.action === 'getStatus') {
      sendResponse({ enabled: isEnabled, settings: overlaySettings });
    }
    return true;
  });

  // Main toggle function
  function toggleInspector() {
    isEnabled = !isEnabled;
    if (isEnabled) {
      scanAndOverlayGrids();
    } else {
      removeAllOverlays();
    }
  }

  // Scan page for grid containers
  function scanAndOverlayGrids() {
    const allElements = document.querySelectorAll('*');
    
    allElements.forEach(element => {
      const computedStyle = window.getComputedStyle(element);
      const display = computedStyle.display;
      
      if (display === 'grid' || display === 'inline-grid') {
        createGridOverlay(element, computedStyle);
      }
    });
  }

  // Create overlay for a grid container
  function createGridOverlay(element, computedStyle) {
    // Get grid properties
    const gridTemplateColumns = computedStyle.gridTemplateColumns;
    const gridTemplateRows = computedStyle.gridTemplateRows;
    const gridTemplateAreas = computedStyle.gridTemplateAreas;
    const columnGap = computedStyle.columnGap;
    const rowGap = computedStyle.rowGap;
    const justifyItems = computedStyle.justifyItems;
    const alignItems = computedStyle.alignItems;

    // Create overlay container
    const overlay = document.createElement('div');
    overlay.className = 'grid-inspector-overlay';
    overlay.style.position = 'absolute';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.pointerEvents = 'none';
    overlay.style.zIndex = '999999';
    overlay.style.boxSizing = 'border-box';

    // Apply overlay settings
    if (overlaySettings.showGridLines) {
      overlay.style.border = `2px solid ${overlaySettings.lineColor}`;
    }
    
    if (overlaySettings.backgroundColor) {
      overlay.style.backgroundColor = overlaySettings.backgroundColor;
    }

    // Position the overlay
    const rect = element.getBoundingClientRect();
    const scrollX = window.scrollX;
    const scrollY = window.scrollY;
    
    overlay.style.left = `${rect.left + scrollX}px`;
    overlay.style.top = `${rect.top + scrollY}px`;
    overlay.style.width = `${rect.width}px`;
    overlay.style.height = `${rect.height}px`;

    // Create track size labels
    if (overlaySettings.showTrackSizes) {
      const columnsLabel = document.createElement('div');
      columnsLabel.className = 'grid-track-label';
      columnsLabel.textContent = `Columns: ${gridTemplateColumns}`;
      columnsLabel.style.position = 'absolute';
      columnsLabel.style.top = '-20px';
      columnsLabel.style.left = '0';
      columnsLabel.style.background = '#333';
      columnsLabel.style.color = '#fff';
      columnsLabel.style.padding = '2px 6px';
      columnsLabel.style.fontSize = '11px';
      columnsLabel.style.borderRadius = '3px';
      overlay.appendChild(columnsLabel);

      const rowsLabel = document.createElement('div');
      rowsLabel.className = 'grid-track-label';
      rowsLabel.textContent = `Rows: ${gridTemplateRows}`;
      rowsLabel.style.position = 'absolute';
      rowsLabel.style.top = '-20px';
      rowsLabel.style.left = '50%';
      rowsLabel.style.background = '#333';
      rowsLabel.style.color = '#fff';
      rowsLabel.style.padding = '2px 6px';
      rowsLabel.style.fontSize = '11px';
      rowsLabel.style.borderRadius = '3px';
      overlay.appendChild(rowsLabel);
    }

    // Add gap information
    if (columnGap !== '0px' || rowGap !== '0px') {
      const gapLabel = document.createElement('div');
      gapLabel.className = 'grid-track-label';
      gapLabel.textContent = `Gap: ${columnGap} / ${rowGap}`;
      gapLabel.style.position = 'absolute';
      gapLabel.style.bottom = '-20px';
      gapLabel.style.left = '0';
      gapLabel.style.background = overlaySettings.gapColor;
      gapLabel.style.color = '#fff';
      gapLabel.style.padding = '2px 6px';
      gapLabel.style.fontSize = '11px';
      gapLabel.style.borderRadius = '3px';
      overlay.appendChild(gapLabel);
    }

    // Store overlay reference
    gridOverlays.set(element, overlay);
    document.body.appendChild(overlay);
  }

  // Remove all overlays
  function removeAllOverlays() {
    const overlays = document.querySelectorAll('.grid-inspector-overlay');
    overlays.forEach(overlay => overlay.remove());
    gridOverlays.clear();
  }

  // Refresh overlays when settings change
  function refreshOverlays() {
    removeAllOverlays();
    if (isEnabled) {
      scanAndOverlayGrids();
    }
  }

  // Observe DOM changes to detect new grids
  const observer = new MutationObserver((mutations) => {
    if (isEnabled) {
      // Debounce the overlay refresh
      clearTimeout(window.gridOverlayTimeout);
      window.gridOverlayTimeout = setTimeout(() => {
        refreshOverlays();
      }, 100);
    }
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['style', 'class']
  });

  console.log('CSS Grid Inspector content script loaded');
})();

This content script provides comprehensive grid detection and visualization. It scans the page for elements with grid or inline-grid display, creates visual overlays showing grid lines, track sizes, and gap information. The script also uses MutationObserver to detect dynamically added or modified grid containers.


The popup provides the user interface for controlling the inspector. Create popup/popup.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS Grid Inspector</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="popup-container">
    <header>
      <h1>CSS Grid Inspector</h1>
    </header>
    
    <main>
      <div class="control-group">
        <label class="toggle-label">
          <input type="checkbox" id="enableInspector">
          <span class="toggle-text">Enable Grid Overlay</span>
        </label>
      </div>

      <div class="settings-section">
        <h2>Overlay Settings</h2>
        
        <div class="control-group">
          <label class="toggle-label">
            <input type="checkbox" id="showGridLines" checked>
            <span>Show Grid Lines</span>
          </label>
        </div>

        <div class="control-group">
          <label class="toggle-label">
            <input type="checkbox" id="showTrackSizes" checked>
            <span>Show Track Sizes</span>
          </label>
        </div>

        <div class="control-group">
          <label class="toggle-label">
            <input type="checkbox" id="showAreaNames">
            <span>Show Area Names</span>
          </label>
        </div>

        <div class="color-settings">
          <label>
            <span>Line Color</span>
            <input type="color" id="lineColor" value="#ff0000">
          </label>
          
          <label>
            <span>Background Color</span>
            <input type="color" id="bgColor" value="#00ffff">
          </label>
          
          <label>
            <span>Gap Color</span>
            <input type="color" id="gapColor" value="#ff00ff">
          </label>
        </div>
      </div>

      <div class="info-section">
        <p>Click on any grid element to see detailed information.</p>
      </div>
    </main>

    <footer>
      <button id="refreshBtn">Refresh</button>
    </footer>
  </div>
  <script src="popup.js"></script>
</body>
</html>

Create popup/popup.css for styling:

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 300px;
  background: #f5f5f5;
}

.popup-container {
  padding: 16px;
}

header h1 {
  font-size: 18px;
  color: #333;
  margin-bottom: 16px;
  padding-bottom: 8px;
  border-bottom: 1px solid #ddd;
}

.settings-section {
  margin-top: 16px;
}

.settings-section h2 {
  font-size: 14px;
  color: #666;
  margin-bottom: 12px;
}

.control-group {
  margin-bottom: 12px;
}

.toggle-label {
  display: flex;
  align-items: center;
  cursor: pointer;
}

.toggle-label input[type="checkbox"] {
  margin-right: 8px;
  width: 16px;
  height: 16px;
}

.toggle-text {
  font-weight: 500;
  color: #333;
}

.color-settings {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #ddd;
}

.color-settings label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-size: 13px;
  color: #555;
}

.color-settings input[type="color"] {
  width: 32px;
  height: 24px;
  border: none;
  cursor: pointer;
}

.info-section {
  margin-top: 16px;
  padding: 12px;
  background: #e8f4fd;
  border-radius: 6px;
}

.info-section p {
  font-size: 12px;
  color: #555;
  line-height: 1.4;
}

footer {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #ddd;
}

#refreshBtn {
  width: 100%;
  padding: 10px;
  background: #4285f4;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.2s;
}

#refreshBtn:hover {
  background: #3367d6;
}

Create popup/popup.js to handle user interactions:

document.addEventListener('DOMContentLoaded', () => {
  const enableInspector = document.getElementById('enableInspector');
  const showGridLines = document.getElementById('showGridLines');
  const showTrackSizes = document.getElementById('showTrackSizes');
  const showAreaNames = document.getElementById('showAreaNames');
  const lineColor = document.getElementById('lineColor');
  const bgColor = document.getElementById('bgColor');
  const gapColor = document.getElementById('gapColor');
  const refreshBtn = document.getElementById('refreshBtn');

  // Load current state
  loadState();

  // Event listeners
  enableInspector.addEventListener('change', () => {
    sendMessage({ action: 'toggleInspector' });
  });

  showGridLines.addEventListener('change', updateSettings);
  showTrackSizes.addEventListener('change', updateSettings);
  showAreaNames.addEventListener('change', updateSettings);
  lineColor.addEventListener('change', updateSettings);
  bgColor.addEventListener('change', updateSettings);
  gapColor.addEventListener('change', updateSettings);

  refreshBtn.addEventListener('click', () => {
    sendMessage({ action: 'toggleInspector' });
    setTimeout(() => {
      sendMessage({ action: 'toggleInspector' });
    }, 100);
  });

  function loadState() {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      if (tabs[0]) {
        chrome.tabs.sendMessage(tabs[0].id, { action: 'getStatus' }, (response) => {
          if (response) {
            enableInspector.checked = response.enabled;
            showGridLines.checked = response.settings.showGridLines;
            showTrackSizes.checked = response.settings.showTrackSizes;
            showAreaNames.checked = response.settings.showAreaNames;
            lineColor.value = rgbToHex(response.settings.lineColor);
            bgColor.value = rgbToHex(response.settings.backgroundColor);
            gapColor.value = rgbToHex(response.settings.gapColor);
          }
        });
      }
    });
  }

  function updateSettings() {
    const settings = {
      showGridLines: showGridLines.checked,
      showTrackSizes: showTrackSizes.checked,
      showAreaNames: showAreaNames.checked,
      lineColor: lineColor.value,
      backgroundColor: bgColor.value,
      gapColor: gapColor.value
    };
    sendMessage({ action: 'updateSettings', settings });
  }

  function sendMessage(message) {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      if (tabs[0]) {
        chrome.tabs.sendMessage(tabs[0].id, message);
      }
    });
  }

  function rgbToHex(rgb) {
    if (rgb.startsWith('#')) return rgb;
    const result = rgb.match(/\d+/g);
    if (result) {
      return '#' + result.slice(0, 3).map(x => {
        const hex = parseInt(x).toString(16);
        return hex.length === 1 ? '0' + hex : hex;
      }).join('');
    }
    return '#ff0000';
  }
});

Creating the Background Service Worker

Create background/background.js to handle extension lifecycle events:

// Background Service Worker for CSS Grid Inspector

chrome.runtime.onInstalled.addListener((details) => {
  console.log('CSS Grid Inspector installed:', details.reason);
});

// Handle extension icon click if no popup is defined
chrome.action.onClicked.addListener((tab) => {
  chrome.tabs.sendMessage(tab.id, { action: 'toggleInspector' });
});

console.log('CSS Grid Inspector background service worker loaded');

Testing Your Extension

Now that we have created all the necessary files, let us test our extension:

  1. Open Chrome and navigate to chrome://extensions/
  2. Enable “Developer mode” in the top right corner
  3. Click “Load unpacked” and select your extension folder
  4. Navigate to a website with CSS Grid layouts
  5. Click the extension icon in your toolbar
  6. Toggle the inspector and observe the grid overlays

You should see visual overlays on all grid containers, displaying grid lines, track sizes, and gap information. Try adjusting the settings in the popup to customize the appearance.


Advanced Features to Consider

While our basic grid inspector works well, there are many advanced features you can add to make it even more powerful:

Grid Area Highlighting: Implement functionality to highlight specific named grid areas when hovering over elements. This helps developers understand complex grid template configurations.

Track Size Visualization: Add visual representations of fr units, pixel values, and auto tracks to help developers understand how space is distributed in their grids.

Gap Editing: Allow users to click on gap areas and adjust gap sizes directly from the overlay, providing an interactive debugging experience.

Export Grid Information: Generate CSS code snippets representing the current grid configuration, helping developers copy grid properties to their stylesheets.

Multiple Overlay Styles: Offer different overlay styles such as dotted lines, solid backgrounds, or numbered grid cells for different debugging needs.


Publishing Your Extension

Once you have tested your extension and added any desired features, you can publish it to the Chrome Web Store:

  1. Create a developer account at the Chrome Web Store Developer Dashboard
  2. Package your extension as a ZIP file
  3. Upload your extension and fill in the required information
  4. Submit for review
  5. Once approved, your extension will be available to millions of Chrome users

Remember to follow Chrome’s policies and ensure your extension provides genuine value to users.


Conclusion

Building a CSS Grid Inspector extension is an excellent project that teaches valuable Chrome extension development skills while creating a genuinely useful tool for web developers. Throughout this guide, we have covered the essential components: manifest configuration, content scripts for DOM interaction, popup interfaces for user control, and background service workers for extension lifecycle management.

The extension we built provides visual overlays for CSS grid layouts, showing grid lines, track sizes, and gap information. These features directly address the pain points developers face when debugging complex grid layouts, making it a valuable addition to any developer’s toolkit.

As you continue to develop Chrome extensions, remember that the best tools often come from solving your own problems. If you find yourself needing additional features while using your grid inspector, consider adding them yourself and contributing back to the developer community.

No previous article
No next article