Search code examples
csscss-grid

How can a CSS grid use the maximum nested element size to render equally sized columns?


See this fiddle.

Consider a grid of words in four columns, with each column being the same width. I can get this with grid-template-columns: repeat(auto-fill, minmax(25%, 1fr)); Most of the time, these will be short words. But there could be a long word which overflows its block:

minmax(25%, 1fr)

If this were static data, I could have just changed the grid css to grid-template-columns: repeat(auto-fill, minmax(33%, 1fr)); to comfortably fill the space:

minmax(33%, 1fr)

How can I make the CSS renderer adjust the number of columns for me, making each the same width, using the width of the widest word block in the set?


Solution

  • I'm surprised that CSS doesn't (as far as I can tell) support this since I think it should be a common use case for wrapping grids. I tinkered until I had a solution with a minimal amount of javascript:

    example fiddle with IIFE

    First, we need to find out how wide the widest string (or could be any block element) will be when rendered on the client. grid-template-columns: repeat(1, max-content); will do that by arranging the elements in a single column, and all elements get the width of the widest one. After the dimensions are established but before being rendered to the screen, grab any element, compute the number of columns that will fit, then change the CSS for the container to have that many columns + 1fr to make them fill the space:

    (function(){
      const ul = document.querySelectorAll('ul')[0];
      const ee = [...document.querySelectorAll('li')];
      const max = ee.map(e=>e.offsetWidth).reduce((a,b)=>Math.max(a, b));
      const cols = Math.min(5,Math.floor(ul.offsetWidth/max));
        ul.style.gridTemplateColumns = "repeat("+cols+", 1fr)";
    })();
    

    This seems to work with no flicker in all evergreen browsers. Angular version I used:

    ngAfterViewInit(): void {
      const ul: HTMLElement = this.ul.nativeElement;
      let cols: number = 0;
      const w: number = ul.children[0].clientWidth;
      if (w > 0) {
        cols = Math.min(5, Math.floor(ul.offsetWidth / w));
      }
      ul.style.gridTemplateColumns = "repeat(" + cols + ",1fr)";
    }