← Visual design
toolshed

Mobile filter bar

When a page has a desktop filter sidebar, mobile gets a compact toggle button and collapsible panel instead.

On desktop, filter controls live in a sidebar. On mobile (800px and below), the sidebar disappears and a “Filters” toggle button takes over. One scroll context throughout.

The layout rule

Desktop (>800px): sidebar visible, mobile bar hidden
Mobile (≤800px):  sidebar hidden, mobile bar shown

Never add overflow, max-height, or position: sticky to either column. One page, one scrollbar.

How the sync works

The mobile panel uses separate inputs from the sidebar. They stay in sync via data attributes:

  • Sidebar inputs get data-filter-group="status" (or whichever group)
  • Mobile inputs get data-mobile-group="status"

When a mobile checkbox changes, find the matching sidebar input and set .checked, then call render(). Since render() always reads from the sidebar inputs, the filter logic needs no changes.

mobileInputs.forEach(mInput => {
  mInput.addEventListener('change', () => {
    const group = mInput.dataset.mobileGroup ?? '';
    const match = sidebarInputs.find(
      i => i.dataset.filterGroup === group && i.value === mInput.value
    );
    if (match) match.checked = mInput.checked;
    updateCount();
    currentPage = 1;
    render();
  });
});

Count badge

The toggle button shows a badge with the number of active filters. Hidden at zero.

function updateCount() {
  const n = mobileInputs.filter(i => i.checked).length;
  countEl.textContent = String(n);
  countEl.hidden = n === 0;
}

Panel background color

Use the page’s own filter color, not a generic one:

  • Stream: #EEF3EB (green-tinted)
  • Library: #F0EAE4 (sand)

Match the sidebar’s folder-tab color so the panel feels like it belongs.

Where used

Homepage stream (groups: Collection, Maturity, Written by) and library page (groups: Status, Format, Topic, Book type).

Mycelium tags, relations & arguments