Kala Tech

Measuring a Horizontal Flex List: Calculating Dynamic Overflows in JavaScript

In modern UI design, horizontal lists of elements (such as filter badges, tags, or tab menus) are commonplace. To prevent them from wrapping to a new line and cluttering the interface, we often want to display them in a single row.

But what happens when the screen or container shrinks? If we simply hide the overflow with overflow: hidden, the items will be cut off mid-word, leaving an unpolished user experience.

A better solution is to measure the elements dynamically and display a "+N More" button representing the hidden items. Today, we will discuss how to write a robust JavaScript layout algorithm to determine exactly how many items fit and how to handle the "+N More" button dimension footprint.


The Challenge

Every element in our list contains different text, meaning their widths are dynamic and unknown at design time. We cannot rely on simple division.

Instead, our algorithm must:

  1. Loop through the list items and measure their individual bounding boxes.
  2. Accumulate their widths along with the gaps between them.
  3. Compare the accumulated width to the parent container's width.
  4. If the total exceeds the parent, calculate how many items can be rendered after allocating space for a "Show More" button.

The Calculation Algorithm

Let's break down the logic. We will assume our "Show More" button has a fixed width of 75px and the gap between items is 8px.

Step 1: Check If Everything Fits

First, we run a check to see if all items fit in the container without modifying anything.

const gap = 8;
const parentWidth = container.clientWidth;
let accumulatedWidth = 0;
let allFit = true;

for (let i = 0; i < items.length; i++) {
  const itemWidth = items[i].getBoundingClientRect().width;
  accumulatedWidth += itemWidth + (i > 0 ? gap : 0);
  
  if (accumulatedWidth > parentWidth) {
    allFit = false;
    break;
  }
}

Step 2: Handle the "Show More" Overflow

If the items do not all fit, we must insert a "Show More" button. Because this button takes up space (75px), the available width for rendering items is reduced:

$$\text{Available Width} = \text{Parent Width} - \text{Button Width} - \text{Gap}$$

We must recalculate how many items fit within this reduced space:

const showMoreWidth = 75;
const availableWidth = parentWidth - showMoreWidth - gap;

let currentWidth = 0;
let fitCount = 0;

for (let i = 0; i < items.length; i++) {
  const itemWidth = items[i].getBoundingClientRect().width;
  const spaceNeeded = itemWidth + (i > 0 ? gap : 0);
  
  if (currentWidth + spaceNeeded > availableWidth) {
    break; // This item (and subsequent ones) will overflow
  }
  
  currentWidth += spaceNeeded;
  fitCount++;
}

Step 3: Toggle Visibility and Insert the Button

Once we know fitCount, we hide any items starting from index fitCount onwards and append our button containing the count of hidden items: items.length - fitCount.


Live Interactive Demo

Resize the box below by dragging the handle in the bottom-right corner. The layout engine will recalculate the fit metrics in real time and display a "+N MORE" button if the items overflow:

React
TypeScript
Performance
CSS Grid
Accessibility
Eleventy
HTML5
Node.js
Web API
Parent: 0px
Fitting: 0 / 0
Space Left: 0px

* Drag bottom-right corner to expand horizontally


Conclusion

Using JavaScript's getBoundingClientRect() in combination with a ResizeObserver allows us to build highly responsive, context-aware navigation elements. Instead of building messy, wrap-around layouts, this approach guarantees that elements fit clean grid alignments perfectly!