Wed Jul 30 2025
software

Simple bento box layout with CSS grid

Jeremy Atkinson
Kinson.io

This quick post summarizes how I solved the problem of creating a psuedo-random bento-box layout for my website.

The "bento box" layout has become a popular design trend for showcasing a variety of content in a visually interesting grid. I wanted to implement something similar for my own website to display a list of blog posts, but I didn't want a static, repetitive layout. I wanted it to feel a bit more dynamic and pseudo-random. This post walks through how I achieved this using CSS Grid and a little bit of Javascript.

The Foundation: CSS Grid

The foundation of the layout is a simple CSS grid container. Using Tailwind CSS, I can define a 4-column grid that adapts to a single column on smaller screens:

<div className="max-w-6xl mx-4 lg:mx-auto my-16 grid grid-cols-1 md:grid-cols-4 gap-4">
  {/* ... cards go here */}
</div>

This gives us a responsive grid that works well across different screen sizes.

Creating Pseudo-Random Patterns

To avoid a monotonous layout, I defined several column-span patterns that cards could follow. Each pattern represents how many columns each card in a row should span:

const patterns = [
  [2, 2],        // Two cards, each spanning 2 columns
  [1, 2, 1],     // Three cards: 1 column, 2 columns, 1 column
  [1, 1, 1, 1],  // Four cards, each spanning 1 column
  [2, 1, 1],     // Three cards: 2 columns, 1 column, 1 column
  [1, 1, 2],     // Three cards: 1 column, 1 column, 2 columns
];

All patterns add up to 4 columns total, ensuring they fit perfectly within our grid.

The Pattern Selection Logic

Here's the function that handles pattern selection and ensures we don't get consecutive identical rows:

function getRandomPattern(currentPattern) {
  const patterns = [[2, 2], [1, 2, 1], [1, 1, 1, 1], [2, 1, 1], [1, 1, 2]];
  const index = Math.round(Math.random() * (patterns.length - 1));
  let newPattern = patterns[index];

  // Avoid consecutive identical patterns
  if (currentPattern && arraysEqual(newPattern, currentPattern)) {
    newPattern = patterns[(index + 1) % patterns.length];
  }
  
  return newPattern;
}

This function randomly selects a pattern, but if it's the same as the current pattern, it picks the next one in the array. This prevents visual monotony while maintaining the pseudo-random feel.

Putting It All Together

The complete implementation maps through the blog posts and applies the patterns dynamically:

let currentPattern = getRandomPattern();
let position = -1;

return (
  <div className="max-w-6xl mx-4 lg:mx-auto my-16 grid grid-cols-1 md:grid-cols-4 lg:grid-cols-4 gap-4">
    {posts.map((post, index) => {
      const span = currentPattern[++position];
      
      // When we reach the end of the current pattern, get a new one
      if (position === currentPattern.length - 1) {
        currentPattern = getRandomPattern(currentPattern);
        position = -1;
      }
      
      return <InteractiveCard key={index} {...post} span={span} />;
    })}
  </div>
);

The InteractiveCard Component

Each card receives a span prop that determines how many columns it should occupy:

function InteractiveCard({ span, ...post }) {
  return (
    <div className={`col-span-${span} ...`} >
      {/* Card content */}
    </div>
  );
}

Helper Function

I was running into some issues with comparing arrays, so made this utility function to compare element values:

function arraysEqual(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (a.length !== b.length) return false;

  for (let i = 0; i < a.length; ++i) {
    if (a[i] !== b[i]) return false;
  }
  
  return true;
}

The Result

This approach creates a visually interesting bento box layout that feels organic and varied while maintaining structural consistency. Each row uses a different pattern from the previous one, and the pseudo-random selection ensures that even with a limited set of patterns, the layout never feels predictable.

What I like about this solution is its simplicity - no libraries required. Just CSS Grid, a few predefined patterns, and some basic JavaScript logic to tie it all together.

bento box layout in action

Potential Enhancements

A few ideas for taking this further:

  • Make it generic for any number of columns
  • Implement different patterns for different screen sizes
  • Spanning rows as well as columns
  • Consider content-aware spanning (longer titles get wider cards)

Jeremy Atkinson

Jeremy is a structural engineer, researcher, and developer from BC. He works on Calcs.app and writes at Kinson.io

Comments (0)

Loading comments...