Responsive Layout for a Resizable Side Panel
Design a Chrome side panel that works from narrow to wide: container queries, fluid typography, collapsing toolbars, list and detail views, keeping state on resize, and testing panel widths.
Table of Contents
The side panel looks great at the default width the designer used. Then a user drags it to its narrowest — around 320 pixels — and the toolbar wraps onto three lines, buttons overflow, and the two-column layout squashes text into one word per line. Another user drags it wide on a large monitor and gets a single narrow column floating in empty space. Unlike the popup, whose size you control, the side panel’s width belongs to the user: Chrome lets them drag the edge between a minimum of roughly 320 px and a large share of the window. A good panel adapts across that range. This guide builds a layout that does. It belongs to side panel and DevTools interfaces.
The side panel’s size constraints
The side panel is an extension page rendered at whatever width the user chooses; height is the full browser window height minus the panel header. The page receives normal resize events and its viewport width changes, so CSS media queries and container queries work. The minimum width is fixed by Chrome; the maximum is generous. Firefox’s sidebar behaves similarly. Design for three bands: narrow (about 320–420 px) where only one column and icon-only toolbars fit, regular (420–640 px) where labels and comfortable spacing fit, and wide (over 640 px) where a list/detail split makes good use of space.
Step-by-step: a panel that adapts
1. Make the page a size container
1/* sidepanel.css */
2html, body { height: 100%; margin: 0; }
3body { display: grid; grid-template-rows: auto 1fr; }
4main { container-type: inline-size; container-name: panel; min-height: 0; overflow: hidden; }
Execution context: side panel CSS. Declaring main as an inline-size container lets components respond to the space they actually have, not the viewport — useful if you later embed the same components in the popup or an options page. min-height: 0 lets the grid row shrink so inner lists scroll instead of the whole page.
2. Lay out list and detail by container width
1.workspace { display: grid; grid-template-columns: 1fr; height: 100%; }
2.list { overflow: auto; }
3.detail { overflow: auto; }
4
5@container panel (max-width: 639px) {
6 .workspace[data-view="list"] .detail { display: none; }
7 .workspace[data-view="detail"] .list { display: none; }
8}
9@container panel (min-width: 640px) {
10 .workspace { grid-template-columns: minmax(240px, 34%) 1fr; }
11 .back-button { display: none; }
12}
Execution context: side panel CSS. Below 640 px the panel shows either the list or the detail, switched with a data-view attribute and a Back button; above it, both appear side by side and the Back button hides. The list column has a minimum width and a proportion, so it neither collapses nor dominates on very wide panels.
3. Collapse the toolbar gracefully
1.toolbar { display: flex; gap: 4px; align-items: center; padding: 6px 8px; container-type: inline-size; }
2.toolbar .label { display: none; }
3@container (min-width: 420px) { .toolbar .label { display: inline; } }
4.toolbar .secondary { display: none; }
5@container (min-width: 520px) { .toolbar .secondary { display: inline-flex; } .toolbar .more .secondary-item { display: none; } }
1<div class="toolbar">
2 <button aria-label="New note"><svg aria-hidden="true" viewBox="0 0 16 16">…</svg><span class="label">New note</span></button>
3 <button class="secondary" aria-label="Export"><svg aria-hidden="true" viewBox="0 0 16 16">…</svg><span class="label">Export</span></button>
4 <details class="more"><summary aria-label="More actions">⋯</summary><button class="secondary-item">Export</button></details>
5</div>
Execution context: the side panel. Every button keeps an aria-label, so hiding the visible label at narrow widths does not remove its accessible name. Secondary actions move into a “More” menu when space is short, and come out when it is available. Avoid wrapping toolbars — they push content down unpredictably as the user resizes.
4. Keep selection and view state across resizes
1// sidepanel.js
2const workspace = document.querySelector(".workspace");
3const state = { selected: null, view: "list" };
4const wide = matchMedia("(min-width: 640px)");
5
6function select(id) {
7 state.selected = id;
8 renderDetail(id);
9 if (!wide.matches) setView("detail");
10}
11function setView(v) { state.view = v; workspace.dataset.view = v; }
12document.querySelector(".back-button").addEventListener("click", () => {
13 setView("list");
14 document.querySelector(`[data-id="${state.selected}"]`)?.scrollIntoView({ block: "nearest" });
15});
16wide.addEventListener("change", () => { if (!wide.matches) setView(state.selected ? "detail" : "list"); });
Execution context: the side panel. CSS handles the arrangement, but which view to show at narrow widths is state. When the panel narrows with an item selected, showing its detail keeps the user’s context; Back returns to the list with the item scrolled into view. The panel viewport and the main container are nearly the same width here, so a media query is a simple way to observe the breakpoint in JavaScript.
5. Use fluid type and a readable line length
1:root { font-size: clamp(13px, 0.6rem + 0.9vw, 15px); }
2.detail article { max-width: 68ch; margin-inline: auto; padding: 12px 16px; }
Execution context: side panel CSS. clamp grows text slightly with width without becoming huge on wide panels. Limiting article width to about 68 characters keeps long text readable when the panel is very wide; centre it so the space looks intentional.
6. Avoid fixed widths and horizontal scrolling
Replace fixed pixel widths with minmax, percentages and fr units; let long URLs and code wrap with overflow-wrap: anywhere; make tables scroll inside their own container rather than the page. At 320 px there should be no horizontal scroll bar on the panel itself.
7. Test at the extremes
Drag the panel to its minimum and to a very wide width and walk through every view. Automate it by loading sidepanel.html in a Playwright page at viewport widths of 320, 420, 640 and 960 pixels and taking screenshots; see visual regression testing for extension UI.
Common mistakes
- Designing for one width. Users resize the panel.
- Wrapping toolbars. Content jumps as the user drags.
- Losing selection on layout switch. Context disappears mid-task.
- Hiding labels without
aria-label. Icon buttons become nameless. - Unbounded line length on wide panels. Text becomes hard to read.
Cross-browser variation
- Chrome / Edge: resizable side panel with a minimum around 320 px; container queries supported.
- Firefox: the sidebar is resizable with a similar minimum; the same CSS works.
- Safari: no side panel; if the same UI is reused in a popover, its fixed size makes the narrow band the one that matters.
Verification
- Drag the panel to its narrowest and confirm no horizontal scroll and a usable toolbar.
- Select an item while wide, then narrow, and confirm the detail stays visible.
- Use a screen reader at narrow width and confirm icon buttons are named.
- Compare screenshots at four widths against the baseline.
FAQ
Can I set the side panel’s width?
No. The user controls it; Chrome remembers the last width.
Media queries or container queries?
Both work in the side panel. Container queries make components reusable in other surfaces.
Should the panel remember the last view?
Store the selected item in storage.session so reopening the panel restores it during the session.
How should dialogs behave in a narrow panel?
Make them full-width sheets that slide up from the bottom (or appear without motion under reduced motion), with the primary action at the bottom within thumb and keyboard reach. At wider widths, centre them with a maximum width.
Do I need a separate mobile design?
No. Chrome on Android does not support extensions’ side panels. The narrow band of your desktop panel is the smallest layout you need.
Related
- Building a side panel UI in MV3 — the panel basics.
- Passing data between side panel and content script — the data shown.
- Fixing popup size and overflow issues — the popup’s fixed-size equivalent.
- Side panel and DevTools interfaces — the parent topic.