Build a Focus Mode Chrome Extension: Distraction-Free Reading Experience

25 min read

Build a Focus Mode Chrome Extension: Distraction-Free Reading Experience

Build a Focus Mode Chrome Extension: Distraction-Free Reading Experience

In an era where digital distractions compete for our attention every second, the ability to focus on reading content without interruptions has become a precious skill. Whether you are researching topics for work, studying educational materials, or simply trying to enjoy a long-form article, the modern web is filled with pop-ups, sidebar advertisements, notification badges, and endless suggested content that fragments your attention. Building a Focus Mode Chrome extension provides users with a clean, distraction-free reading environment that enhances comprehension and productivity.

This comprehensive guide walks you through creating a fully functional Focus Mode Chrome extension from scratch. We will explore the technical architecture, implement core features using Manifest V3, and create an intuitive user interface that transforms cluttered web pages into serene reading experiences. By the end of this tutorial, you will have a complete, deployable extension that users can install to dramatically improve their reading sessions.


Understanding Focus Mode and Its Importance

The Problem with Modern Web Design

Today’s websites are designed with monetization and engagement as primary goals, not user experience or reading comfort. The typical news article now includes multiple advertisements interspersed with content, social media sharing buttons that follow you as you scroll, related articles pop-ups that interrupt your flow, newsletter subscription modals that block your view, and auto-playing videos that hijack your audio. For readers seeking to consume content efficiently, these elements create significant friction.

Research consistently shows that the human brain struggles to maintain focus when faced with visual clutter and interruptions. Each distraction requires mental energy to dismiss or ignore, depleting cognitive resources that could be directed toward understanding and retaining the actual content. This is particularly problematic for long-form content such as in-depth articles, technical documentation, and educational materials where sustained attention is essential for comprehension.

The Solution: A Dedicated Focus Mode Extension

A well-designed Focus Mode extension addresses these issues by providing users with a streamlined reading environment. The core philosophy is simple: present only the content the user wants to read, remove everything else. This means hiding or neutralizing advertisements, removing navigation elements that might lead away from the current page, eliminating social sharing widgets, blocking auto-playing media, and applying typography choices that optimize readability.

What makes Focus Mode particularly valuable is its accessibility. Users should be able to activate it with a single click, and it should work automatically across all websites without requiring configuration. The extension should intelligently identify the main content area of any page and present it in an optimal format, regardless of how poorly the original website was designed for reading.


Technical Architecture Overview

Manifest V3 Requirements

Our Focus Mode extension will be built using Chrome’s Manifest V3, which represents the current standard for Chrome extension development. Manifest V3 introduces several important changes from its predecessor, including enhanced security requirements, modified background script behavior, and new capabilities for content scripts.

The manifest file serves as the blueprint for our extension, declaring permissions, defining the extension’s components, and specifying how different parts of the extension interact. For our Focus Mode extension, we will need permissions to access the active tab’s content, inject scripts into web pages, and store user preferences.

Core Components

Our extension consists of four primary components that work together to deliver the focus reading experience:

The popup interface provides users with controls to toggle Focus Mode on and off, adjust settings such as font size and line spacing, and choose from different reading themes. This is the primary user-facing component that appears when clicking the extension icon in Chrome’s toolbar.

The content script runs directly within web pages, performing the actual transformation of the page into focus mode. This script analyzes the page structure, identifies the main content, and applies styling modifications to create the distraction-free environment.

The background service worker handles communication between the popup and content scripts, maintains the extension’s state, and manages any browser-level functionality such as keyboard shortcuts.

The options page allows users to customize their Focus Mode experience with preferences for typography, color schemes, and behavior settings.


Step-by-Step Implementation

Setting Up the Project Structure

Begin by creating a new directory for your extension project. Inside this directory, create the following file structure:

focus-mode-extension/
├── manifest.json
├── popup.html
├── popup.css
├── popup.js
├── content.js
├── background.js
├── options.html
├── options.js
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

This structure keeps our extension organized and maintainable, with clear separation between the user interface, content manipulation logic, and background processing.

Creating the Manifest

The manifest.json file defines our extension’s capabilities and structure. Here is the complete implementation:

{
  "manifest_version": 3,
  "name": "Focus Mode - Distraction-Free Reading",
  "version": "1.0.0",
  "description": "Transform any webpage into a clean, distraction-free reading experience",
  "permissions": [
    "activeTab",
    "scripting",
    "storage"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "options_page": "options.html",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

This manifest declares the minimum permissions required for our extension to function. We use the activeTab permission to access only the current tab when the user activates the extension, and scripting permission to inject our content script.

Building the Popup Interface

The popup provides the primary user interface for controlling Focus Mode. Create popup.html with the following structure:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>Focus Mode</h1>
    <div class="toggle-container">
      <label class="switch">
        <input type="checkbox" id="focusToggle">
        <span class="slider"></span>
      </label>
      <span class="status">Off</span>
    </div>
    
    <div class="settings">
      <div class="setting-group">
        <label>Font Size</label>
        <input type="range" id="fontSize" min="14" max="24" value="18">
        <span id="fontSizeValue">18px</span>
      </div>
      
      <div class="setting-group">
        <label>Theme</label>
        <select id="themeSelect">
          <option value="light">Light</option>
          <option value="sepia">Sepia</option>
          <option value="dark">Dark</option>
        </select>
      </div>
      
      <div class="setting-group">
        <label>Line Height</label>
        <input type="range" id="lineHeight" min="1.2" max="2.0" step="0.1" value="1.6">
        <span id="lineHeightValue">1.6</span>
      </div>
    </div>
    
    <button id="saveSettings">Save Settings</button>
  </div>
  <script src="popup.js"></script>
</body>
</html>

The popup presents users with a clean, intuitive interface containing a prominent toggle switch to activate or deactivate Focus Mode, followed by customization options for font size, theme, and line spacing. This design ensures users can quickly enable focus mode while also providing depth for those who want to customize their reading experience.

Styling the Popup

Create popup.css to style our interface:

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 300px;
  padding: 20px;
  background: #ffffff;
  color: #333;
}

h1 {
  font-size: 20px;
  margin-bottom: 20px;
  color: #1a73e8;
}

.toggle-container {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
}

.switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 28px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 34px;
}

.slider:before {
  position: absolute;
  content: "";
  height: 20px;
  width: 20px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}

input:checked + .slider {
  background-color: #1a73e8;
}

input:checked + .slider:before {
  transform: translateX(22px);
}

.status {
  font-weight: 500;
  font-size: 14px;
}

.settings {
  margin-bottom: 20px;
}

.setting-group {
  margin-bottom: 16px;
}

.setting-group label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 8px;
  color: #555;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.setting-group select,
.setting-group input[type="range"] {
  width: 100%;
}

.setting-group span {
  display: block;
  font-size: 12px;
  color: #777;
  margin-top: 4px;
}

button {
  width: 100%;
  padding: 12px;
  background: #1a73e8;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;
}

button:hover {
  background: #1557b0;
}

The styling creates a professional, modern interface that aligns with Chrome’s design language while providing clear visual feedback for all interactions.

The popup.js file handles user interactions and communicates with other extension components:

document.addEventListener('DOMContentLoaded', function() {
  const focusToggle = document.getElementById('focusToggle');
  const fontSizeSlider = document.getElementById('fontSize');
  const fontSizeValue = document.getElementById('fontSizeValue');
  const lineHeightSlider = document.getElementById('lineHeight');
  const lineHeightValue = document.getElementById('lineHeightValue');
  const themeSelect = document.getElementById('themeSelect');
  const saveButton = document.getElementById('saveSettings');
  const statusSpan = document.querySelector('.status');

  // Load saved settings
  chrome.storage.sync.get(['focusEnabled', 'fontSize', 'lineHeight', 'theme'], function(result) {
    if (result.focusEnabled) {
      focusToggle.checked = true;
      statusSpan.textContent = 'On';
    }
    if (result.fontSize) {
      fontSizeSlider.value = result.fontSize;
      fontSizeValue.textContent = result.fontSize + 'px';
    }
    if (result.lineHeight) {
      lineHeightSlider.value = result.lineHeight;
      lineHeightValue.textContent = result.lineHeight;
    }
    if (result.theme) {
      themeSelect.value = result.theme;
    }
  });

  // Update font size display
  fontSizeSlider.addEventListener('input', function() {
    fontSizeValue.textContent = this.value + 'px';
  });

  // Update line height display
  lineHeightSlider.addEventListener('input', function() {
    lineHeightValue.textContent = this.value;
  });

  // Toggle Focus Mode
  focusToggle.addEventListener('change', function() {
    const enabled = this.checked;
    statusSpan.textContent = enabled ? 'On' : 'Off';
    
    chrome.storage.sync.set({ focusEnabled: enabled });
    
    // Get current tab and send message to content script
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
      if (tabs[0]) {
        chrome.tabs.sendMessage(tabs[0].id, {
          action: 'toggleFocusMode',
          enabled: enabled
        });
      }
    });
  });

  // Save settings
  saveButton.addEventListener('click', function() {
    const settings = {
      fontSize: fontSizeSlider.value,
      lineHeight: lineHeightSlider.value,
      theme: themeSelect.value
    };
    
    chrome.storage.sync.set(settings);
    
    // Send updated settings to content script
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
      if (tabs[0]) {
        chrome.tabs.sendMessage(tabs[0].id, {
          action: 'updateSettings',
          settings: settings
        });
      }
    });
    
    saveButton.textContent = 'Saved!';
    setTimeout(() => {
      saveButton.textContent = 'Save Settings';
    }, 1500);
  });
});

The popup script manages state persistence using Chrome’s storage API, ensuring that user preferences are remembered across browser sessions. It also handles real-time communication with the content script to apply changes immediately.

The Content Script: Core Focus Mode Logic

The content script is where the magic happens. This JavaScript file runs within web pages and performs the actual transformation to create the distraction-free reading experience:

// Default settings
let currentSettings = {
  fontSize: 18,
  lineHeight: 1.6,
  theme: 'light'
};

let focusModeActive = false;

// Theme configurations
const themes = {
  light: {
    background: '#ffffff',
    text: '#333333',
    link: '#1a73e8'
  },
  sepia: {
    background: '#f4ecd8',
    text: '#5b4636',
    link: '#2d5a27'
  },
  dark: {
    background: '#1a1a1a',
    text: '#e0e0e0',
    link: '#8ab4f8'
  }
};

// Message listener for communication with popup
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.action === 'toggleFocusMode') {
    focusModeActive = request.enabled;
    if (focusModeActive) {
      activateFocusMode();
    } else {
      deactivateFocusMode();
    }
  } else if (request.action === 'updateSettings') {
    currentSettings = request.settings;
    if (focusModeActive) {
      applyFocusModeStyles();
    }
  }
});

function activateFocusMode() {
  // Find the main content
  const mainContent = findMainContent();
  
  if (!mainContent) {
    console.log('Focus Mode: Could not find main content');
    return;
  }

  // Create focus mode container
  const focusContainer = document.createElement('div');
  focusContainer.id = 'focus-mode-container';
  
  // Wrap the main content
  const wrapper = document.createElement('div');
  wrapper.id = 'focus-mode-content';
  wrapper.innerHTML = mainContent.innerHTML;
  
  focusContainer.appendChild(wrapper);
  
  // Hide original content and show focus mode
  mainContent.style.display = 'none';
  document.body.insertBefore(focusContainer, document.body.firstChild);
  
  // Apply styles
  applyFocusModeStyles();
  
  // Add close button
  addCloseButton();
}

function findMainContent() {
  // Try common content selectors
  const selectors = [
    'article',
    '[role="main"]',
    'main',
    '.post-content',
    '.article-content',
    '.entry-content',
    '.content',
    '#content',
    '.post',
    '.article'
  ];

  for (const selector of selectors) {
    const element = document.querySelector(selector);
    if (element && element.textContent.trim().length > 500) {
      return element;
    }
  }

  // Fallback: find the element with the most text
  const paragraphs = document.querySelectorAll('p');
  let maxText = 0;
  let bestParent = null;

  paragraphs.forEach(p => {
    const parent = p.parentElement;
    if (parent && parent.textContent.length > maxText) {
      maxText = parent.textContent.length;
      bestParent = parent;
    }
  });

  return bestParent;
}

function applyFocusModeStyles() {
  const content = document.getElementById('focus-mode-content');
  const theme = themes[currentSettings.theme] || themes.light;

  if (content) {
    content.style.cssText = `
      max-width: 750px;
      margin: 0 auto;
      padding: 60px 40px;
      background: ${theme.background};
      color: ${theme.text};
      font-family: Georgia, 'Times New Roman', serif;
      font-size: ${currentSettings.fontSize}px;
      line-height: ${currentSettings.lineHeight};
    `;

    // Style headings
    const headings = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
    headings.forEach(h => {
      h.style.color = theme.text;
      h.style.marginTop = '1.5em';
      h.style.marginBottom = '0.5em';
    });

    // Style links
    const links = content.querySelectorAll('a');
    links.forEach(a => {
      a.style.color = theme.link;
      a.style.textDecoration = 'underline';
    });

    // Style images
    const images = content.querySelectorAll('img');
    images.forEach(img => {
      img.style.maxWidth = '100%';
      img.style.height = 'auto';
      img.style.display = 'block';
      img.style.margin = '20px auto';
    });

    // Style paragraphs
    const paragraphs = content.querySelectorAll('p');
    paragraphs.forEach(p => {
      p.style.marginBottom = '1.2em';
    });

    // Style lists
    const lists = content.querySelectorAll('ul, ol');
    lists.forEach(list => {
      list.style.marginBottom = '1em';
      list.style.paddingLeft = '1.5em';
    });

    // Style blockquotes
    const blockquotes = content.querySelectorAll('blockquote');
    blockquotes.forEach(bq => {
      bq.style.borderLeft = `4px solid ${theme.link}`;
      bq.style.paddingLeft = '1em';
      bq.style.fontStyle = 'italic';
      bq.style.opacity = '0.9';
    });
  }

  // Style the container
  const container = document.getElementById('focus-mode-container');
  if (container) {
    container.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: ${theme.background};
      overflow-y: auto;
      z-index: 999999;
    `;
  }
}

function addCloseButton() {
  const container = document.getElementById('focus-mode-container');
  if (!container) return;

  const closeButton = document.createElement('button');
  closeButton.id = 'focus-mode-close';
  closeButton.innerHTML = '✕ Exit Focus Mode';
  closeButton.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    padding: 12px 24px;
    background: rgba(0, 0, 0, 0.1);
    color: inherit;
    border: none;
    border-radius: 6px;
    font-size: 14px;
    cursor: pointer;
    z-index: 1000000;
    transition: background 0.2s;
  `;

  closeButton.addEventListener('click', function() {
    deactivateFocusMode();
    // Notify popup that focus mode is deactivated
    chrome.runtime.sendMessage({ action: 'focusModeDeactivated' });
  });

  container.appendChild(closeButton);
}

function deactivateFocusMode() {
  const container = document.getElementById('focus-mode-container');
  if (container) {
    // Find and restore the original content
    const mainContent = findMainContent();
    if (mainContent) {
      const focusContent = document.getElementById('focus-mode-content');
      if (focusContent) {
        mainContent.innerHTML = focusContent.innerHTML;
        mainContent.style.display = '';
      }
    }
    container.remove();
  }
  focusModeActive = false;
}

// Check if focus mode was enabled on page load
chrome.storage.sync.get(['focusEnabled', 'fontSize', 'lineHeight', 'theme'], function(result) {
  if (result.focusEnabled) {
    focusModeActive = true;
    currentSettings = {
      fontSize: result.fontSize || 18,
      lineHeight: result.lineHeight || 1.6,
      theme: result.theme || 'light'
    };
    activateFocusMode();
  }
});

This content script implements intelligent content detection by searching for common article containers and falling back to analyzing text content to identify the main reading area. Once identified, it creates an overlay that presents only the relevant content with customizable typography and theme options.

Background Service Worker

The background.js file handles extension-wide events and facilitates communication:

// Listen for messages from content script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.action === 'focusModeDeactivated') {
    // Update storage to reflect current state
    chrome.storage.sync.set({ focusEnabled: false });
    
    // Notify popup if open
    chrome.runtime.sendMessage({
      action: 'focusModeStateChanged',
      enabled: false
    });
  }
});

// Handle keyboard shortcut (Ctrl+Shift+F)
chrome.commands.onCommand.addListener(function(command) {
  if (command === 'toggle-focus-mode') {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
      if (tabs[0]) {
        chrome.storage.sync.get('focusEnabled', function(result) {
          const newState = !result.focusEnabled;
          chrome.storage.sync.set({ focusEnabled: newState });
          chrome.tabs.sendMessage(tabs[0].id, {
            action: 'toggleFocusMode',
            enabled: newState
          });
        });
      }
    });
  }
});

Testing Your Extension

Loading the Extension in Chrome

To test your extension during development, open Chrome and navigate to chrome://extensions/. Enable Developer mode using the toggle in the top right corner. Click the Load unpacked button and select your extension’s directory. Your Focus Mode extension should now appear in the toolbar.

Testing the Functionality

Click the extension icon to open the popup. Toggle Focus Mode on and navigate to various websites to test how well it identifies content. Try different settings for font size, line height, and themes. Test the keyboard shortcut if you configured one.

Pay attention to how the extension handles different types of websites: news articles, blog posts, documentation, and long-form content. Note any areas where content detection might need improvement and refine your selectors accordingly.

Publishing to the Chrome Web Store

Once you are satisfied with your extension, you can publish it to the Chrome Web Store. Create your icons at the required sizes (16, 48, and 128 pixels), then zip your extension directory. Navigate to the Chrome Web Store developer dashboard, create a new listing, upload your zip file, and complete the required information. After review, your extension will be available for installation by anyone.


Advanced Features to Consider

As you enhance your Focus Mode extension, consider adding features such as text-to-speech support for hands-free reading, customizable keyboard shortcuts, automatic activation rules for specific websites, reading progress tracking, highlighting and note-taking capabilities, and export functionality to save focused articles for offline reading.


Conclusion

Building a Focus Mode Chrome extension is both a practical project that solves a real problem and an excellent way to learn Chrome extension development. The techniques covered in this guide—including Manifest V3 configuration, content script injection, popup interface design, and user preference storage—form the foundation for building more sophisticated extensions.

The completed extension provides immediate value to users seeking distraction-free reading experiences while demonstrating the power of Chrome’s extension APIs. As you continue to develop and refine the extension based on user feedback, you will gain deeper insights into browser extension architecture and the nuances of creating exceptional user experiences on the web.

No previous article
No next article