Arranging diamond tiles in a grid

Some weeks ago I added webmentions support to my website - which is coincidentally the site you are browsing right now. To pick up on the diamond shape used in the logo of the site, I wanted the avatars/user images of comments, mentions, and backlinks to be cropped to a diamond shape, i.e. a square rotated by 45 degrees. I already wrote a bit about shaping and animating that shape.

<ul class="diamonds">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
.diamonds {
--sq: 60px;
--sq-gap: 4px;
display: flex;
gap: var(--sq-gap);
}
.diamonds li {
width: var(--sq);
height: var(--sq);
display: flex;
align-items: center;
justify-content: center;
margin-inline-end: 4px;
background: lightsalmon;
color: slateblue;
clip-path: polygon(
50% 0%,
100% 50%,
50% 100%,
0% 50%
);
}
  • 1
  • 2
  • 3
  • 4
  • 5

When there are multiple images to be displayed next to each other, the diamond shape causes too much white space between the list elements for my taste. I thought it would be neat, to arrange the squares in a tightly packed grid like tiles on a floor. To do this, I set the inline margin of each square -50% of its with, and the block margin of every second square to -50% of its height.

.diamonds.tiled {
padding-inline-start: calc(0.5 * var(--sq));
padding-block-start: calc(0.5 * var(--sq));
}
.diamonds.tiled li {
margin-inline-start: calc(-0.5 * var(--sq));
}
.diamonds.tiled li:nth-child(even) {
margin-block-start: calc(-0.5 * var(--sq) - 0.5 * var(--sq-gap));
background-color: palegoldenrod;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

That looks nice, but what for the uncertain but not totally impossible scenario where one of my posts gets shared and liked a dozen times? Depending on the available space, the diamonds would sooner or later get squashed because they are layed out with flexbox. To perform some premature optimization, I allow the items to wrap. For illustrative purposes, let's set the width of the list element to cause a wrap and show a red border.

.diamonds.wrapping {
flex-wrap: wrap;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Ouch, in cases where the wrap happens after an odd numbered square, elements after the wrap will overlap with preceding ones. How can we make sure to that the wrap happens after an even numbered element? My first instinct was to use an :nth-child(even) CSS selector and some clever properties but this lead to nothing.

What I finally came up with was to wrap the list with a container element which defines a grid layout. The first column of that grid has half the width of one of the squares. It is followed by an auto-repeated number of columns where each has the width of a whole square with a gap matching the margin between two of them. The child list element .diamonds is set to stretch over all columns of this grid. This works because the width of the diamonds is known.

<div class="diamond-container">
<ul class="diamonds tiled wrapping">
<li>1</li>
<li>2</li>
<li>etc.</li>
</ul>
</div>
.diamond-container {
display: grid;
grid-template-columns:
calc(0.5 * var(--sq))
repeat(auto-fill, calc(var(--sq) + 2 * var(--sq-gap)));
}
.diamond-container .diamonds {
grid-column-start: 1;
grid-column-end: -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Although I'm not a big fan of adding additional markup in order to achieve some visual effect, I'm quite happy with this solution.
Hopefully you can observe it in full effect below in the Feedback section.

Addendum, 2023-02-21 9:30

Similar layouts have been built before but with different approaches:

Jesse Breneman used a sophisticated grid where he places hexagonal shapes.

Temani Afif uses the `shape-outside` property to create a jagged element and floats various shapes around it.

This article has been published on on my blog. Here's a list of all articles if you're interested in this kind of stuff.

Tags: CSS