Adaptive UI for Chrome Extensions: Building Responsive Popups, Sidepanels and Content Scripts (2026)

AppBooster Team · · 9 min read
Responsive web design on multiple screens

Chrome extension UIs exist in a uniquely constrained world. Your popup might be crammed into a 300px-wide panel one moment and stretched across a floating sidepanel the next. Content script overlays get injected into websites with wildly different layouts, font scales, and color schemes. Users run Chrome on 4K monitors, 1080p laptops, high-DPI displays, and everything in between.

Yet most extension tutorials still treat UI as an afterthought — a fixed-size popup that looks fine on the developer’s screen and breaks everywhere else.

This guide covers how to build genuinely adaptive extension UIs using modern CSS techniques, including container queries, logical properties, and prefers-color-scheme — so your extension looks polished no matter where it runs.


Why Adaptive UI Matters for Extensions

Before diving into code, it is worth understanding the unique constraints that make extension UI different from regular web development.

Popups have hard width limits. Chrome enforces a maximum popup width of 800px and height of 600px. But most users never resize their popup — it defaults to whatever width you define in CSS. Too narrow and your UI feels cramped. Too wide and it looks awkward next to the browser toolbar.

Sidepanels are a different surface. The Chrome Side Panel API (introduced in Chrome 114) gives your extension a persistent panel docked to the side of the browser. Users can resize this panel horizontally, so your layout needs to adapt gracefully between a narrow ~250px state and a wider ~500px state.

Content scripts share the page. Overlays and injected UI live inside a host page you don’t control. The host page might have font-size: 18px set globally, or a z-index war, or a dark background that makes your light-themed widget unreadable.

Display density varies. A user on a Retina/HiDPI display at 2x scaling effectively has half the logical pixels. Icons, borders, and images need to look sharp at multiple resolutions.

Adaptive UI means building for all of these conditions from the start, not bolting on fixes after users complain.


CSS Container Queries: The Right Tool for Extension Layouts

Media queries check the viewport width, which is not very useful inside an extension popup (the viewport is the popup). Container queries check the size of a specific element, which maps perfectly to extension UI surfaces.

Setting Up Container Queries in Your Popup

/* popup.css */

/* Define the popup root as a container */
.popup-root {
  container-type: inline-size;
  container-name: popup;
  width: 100%;
  min-width: 280px;
  max-width: 600px;
}

/* Default: compact single-column layout */
.popup-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding: 12px;
}

/* Wider popup: two-column layout */
@container popup (min-width: 400px) {
  .popup-grid {
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    padding: 16px;
  }
}

/* Even wider: three columns with sidebar */
@container popup (min-width: 520px) {
  .popup-grid {
    grid-template-columns: 180px 1fr 1fr;
  }
}
<!-- popup.html -->
<body>
  <div class="popup-root">
    <header class="popup-header">
      <h1>My Extension</h1>
    </header>
    <main class="popup-grid">
      <div class="card">Feature A</div>
      <div class="card">Feature B</div>
      <div class="card">Feature C</div>
    </main>
  </div>
</body>

Container queries are supported in Chrome 105+, which covers the vast majority of extension users in 2026. You can use them without a polyfill.

Tip: Wrap nested components in their own containers too. A card component that is a container can adapt its internal layout independently of the popup’s overall size.


Responsive Sidepanel Layouts

The sidepanel is the most demanding surface to design for because users actively resize it. Think of it like a responsive web page in a narrow column — layouts need to reflow, not just scale down.

Fluid Typography and Spacing

Use clamp() to make type and spacing scale smoothly between sidepanel size extremes:

/* sidepanel.css */

:root {
  --text-base: clamp(13px, 1.5vw, 16px);
  --space-sm: clamp(6px, 1vw, 10px);
  --space-md: clamp(10px, 2vw, 18px);
}

body {
  font-size: var(--text-base);
  padding: var(--space-md);
}

Adaptive Navigation

At narrow widths, a full sidebar nav collapses into an icon-only rail. At wider widths, it shows labels.

.nav {
  container-type: inline-size;
  container-name: sidepanel-nav;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
}

.nav-label {
  display: none;
  white-space: nowrap;
}

@container sidepanel-nav (min-width: 200px) {
  .nav-label {
    display: inline;
  }
}
// sidepanel.js — observe panel resize and update state
const resizeObserver = new ResizeObserver(entries => {
  for (const entry of entries) {
    const width = entry.contentRect.width;
    document.body.dataset.panelSize = width < 300 ? 'narrow' : 'wide';
  }
});

resizeObserver.observe(document.body);

Using a data- attribute on body alongside CSS container queries gives you both CSS-driven and JS-driven adaptation in sync.


Content Script Overlay Positioning

Injected UI is the trickiest surface. You are a guest in someone else’s page and you need to stay out of the way while remaining accessible.

Shadow DOM for Style Isolation

Always wrap injected UI in a Shadow DOM to prevent the host page’s CSS from leaking in:

// content-script.js

function createOverlay() {
  const host = document.createElement('div');
  host.id = 'my-extension-host';

  // Position the host element
  Object.assign(host.style, {
    position: 'fixed',
    bottom: '20px',
    right: '20px',
    zIndex: '2147483647', // Max z-index
    width: '0',
    height: '0',
  });

  document.body.appendChild(host);

  // Attach shadow root
  const shadow = host.attachShadow({ mode: 'closed' });

  // Inject styles into shadow root
  const style = document.createElement('style');
  style.textContent = getOverlayStyles();
  shadow.appendChild(style);

  const panel = document.createElement('div');
  panel.className = 'overlay-panel';
  shadow.appendChild(panel);

  return { shadow, panel };
}

function getOverlayStyles() {
  return `
    .overlay-panel {
      position: fixed;
      bottom: 20px;
      right: 20px;
      width: 320px;
      max-height: 480px;
      background: var(--overlay-bg, #ffffff);
      color: var(--overlay-text, #1a1a1a);
      border-radius: 12px;
      box-shadow: 0 8px 32px rgba(0,0,0,0.18);
      overflow: hidden;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      font-size: 14px;
    }
  `;
}

Shadow DOM means your styles are completely isolated. The host page cannot break your layout, and your styles cannot accidentally override the host page.

Avoiding Layout Conflicts

Some pages use transform or overflow: hidden on ancestors, which breaks position: fixed. Detect this and fall back gracefully:

function hasTransformAncestor(el) {
  let node = el.parentElement;
  while (node && node !== document.documentElement) {
    const style = window.getComputedStyle(node);
    if (style.transform !== 'none' || style.willChange === 'transform') {
      return true;
    }
    node = node.parentElement;
  }
  return false;
}

// If fixed positioning is broken, fall back to absolute on document.body
const useFixed = !hasTransformAncestor(document.body);
host.style.position = useFixed ? 'fixed' : 'absolute';

Dark Mode and Theme Adaptation

Extensions must respect the user’s system color scheme. Hardcoding light colors alienates dark mode users and looks jarring on OLED displays.

CSS Custom Properties with prefers-color-scheme

/* shared theme tokens */
:root {
  --color-bg: #ffffff;
  --color-surface: #f4f4f5;
  --color-text-primary: #18181b;
  --color-text-secondary: #71717a;
  --color-accent: #4f46e5;
  --color-border: #e4e4e7;
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #18181b;
    --color-surface: #27272a;
    --color-text-primary: #fafafa;
    --color-text-secondary: #a1a1aa;
    --color-accent: #818cf8;
    --color-border: #3f3f46;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
  }
}

For content scripts injected into Shadow DOM, prefers-color-scheme still works inside the shadow root — no extra configuration needed.

Syncing with Chrome’s Theme

You can also read Chrome’s current color scheme via the chrome.action API or by checking window.matchMedia:

// background.js or popup.js
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

function applyTheme(isDark) {
  document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
}

applyTheme(prefersDark.matches);
prefersDark.addEventListener('change', e => applyTheme(e.matches));

Then in CSS:

[data-theme="dark"] {
  --color-bg: #18181b;
  /* ...rest of dark tokens */
}

This approach lets you override the automatic detection with a user preference toggle while still defaulting to the system setting.

Tip: Store the user’s explicit theme preference in chrome.storage.sync so it persists across devices and browser sessions.


High-DPI and Icon Sharpness

Blurry icons are an instant sign of a low-quality extension. Use SVG for all UI icons — they scale infinitely without blurring.

For raster images (screenshots, previews), use srcset or the CSS image-set() function:

.hero-image {
  background-image: image-set(
    url('/images/preview.png') 1x,
    url('/images/preview@2x.png') 2x,
    url('/images/preview@3x.png') 3x
  );
  background-size: contain;
}

For extension toolbar icons, always provide all four sizes in your manifest:

{
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

Testing Across Screen Sizes

Adaptive UI is only as good as your testing coverage. Here is a practical testing workflow:

1. DevTools for Popups

Right-click your popup and select “Inspect”. Chrome DevTools opens with the popup as the document. You can resize the popup window manually while DevTools is open to test container queries firing at different widths.

2. Simulate Sidepanel Widths

Open your sidepanel and use DevTools to resize the panel by dragging its edge. Watch for layout breaks at 250px (minimum Chrome allows), 350px, and 500px.

3. Test on Real Sites for Content Scripts

Inject your content script on at least these categories of pages:

  • Dark-background sites (GitHub, Vercel dashboard)
  • Sites with large base font sizes (some news sites set font-size: 20px)
  • Single-page apps with aggressive z-index stacking (Gmail, Notion)
  • Sites using CSS resets that strip all inherited styles

4. Automated Visual Regression

Use Puppeteer to capture screenshots of your popup at multiple sizes as part of CI:

// test/visual-regression.js
const puppeteer = require('puppeteer');

const POPUP_WIDTHS = [280, 360, 480, 600];

(async () => {
  const browser = await puppeteer.launch({
    args: [`--load-extension=./dist`, '--disable-extensions-except=./dist']
  });

  for (const width of POPUP_WIDTHS) {
    const page = await browser.newPage();
    await page.setViewport({ width, height: 600 });
    await page.goto(`chrome-extension://${EXT_ID}/popup.html`);
    await page.screenshot({ path: `screenshots/popup-${width}.png` });
  }

  await browser.close();
})();

Compare screenshots between releases to catch visual regressions before they reach users.


Putting It All Together

Building an adaptive extension UI comes down to a few key principles:

  • Use container queries instead of media queries for popup and sidepanel layout decisions — they respond to the element, not the viewport.
  • Isolate content script styles with Shadow DOM so the host page cannot interfere with your UI.
  • Design with CSS custom properties from day one so theme switching (light/dark, custom accent colors) is a single variable change.
  • Use SVG for icons and provide multi-resolution rasters for any bitmap images.
  • Test on real diverse pages — your content script will encounter things your local test pages never will.

The Chrome extension platform is evolving fast. Sidepanels, offscreen documents, and expanded popup capabilities mean extensions are becoming first-class UI surfaces, not afterthoughts. Investing in adaptive UI now means your extension works beautifully for every user, on every setup — and that is a real competitive advantage in the Chrome Web Store.


Building an extension and want it reviewed before publishing? Check out ExtensionBooster for expert Chrome extension code reviews and store listing optimization.

Share this article

Build better extensions with free tools

Icon generator, MV3 converter, review exporter, and more — no signup needed.

Related Articles