6 Pure CSS1 with JSPublished 2026-10-05Free · no sign-up
Seven card grid layouts: a grid that sizes its own columns, cards whose parts line up with subgrid, a swipeable row on phones that becomes a grid on wide screens, a featured first card, a grid and list view toggle, cards with footers pinned to the bottom and a grid divided by hairlines.
The card grid most sites need: repeat(auto-fill, minmax(min(180px, 100%), 1fr)) fits as many columns as there is room for, with no media queries, and the min() keeps a single card from overflowing very narrow containers.
Cards in a row line up their images, titles, descriptions and buttons, whatever the text lengths, because each card is a subgrid spanning four row tracks of the parent. Without subgrid, the buttons would sit at different heights.
View code
<ul class="cg2"><li><div class="im" aria-hidden="true"></div><h3>Ceramics</h3><p>Hand-thrown pieces.</p><a href="#c">Browse</a></li><li><div class="im" aria-hidden="true"></div><h3>Textiles and soft furnishings</h3><p>Linen, wool and organic cotton for every room.</p><a href="#t">Browse</a></li><li><div class="im" aria-hidden="true"></div><h3>Lighting</h3><p>Lamps.</p><a href="#l">Browse</a></li></ul>
On narrow containers the cards form a horizontal swipeable row with scroll-snap, which saves vertical space on phones; on wide ones they become a normal grid. One container query switches the display, and the row is a labelled, focusable region.
The first card spans two columns and two rows as the featured item, and the rest flow around it. :first-child does the work, so whichever item is first in the data becomes the feature without any extra markup.
A pair of view buttons switches the same cards between a grid and a list. Only a class on the container changes, and the buttons report the current view with aria-pressed, a pattern for shops and file managers.
var ul = document.querySelector('.cg5 ul'), bs = document.querySelectorAll('.cg5 .v button');
bs.forEach(function (b) { b.onclick = function () { ul.className = b.dataset.v; bs.forEach(function (x) { x.setAttribute('aria-pressed', x === b); }); }; });
06. Cards with Pinned Footers
Card gridFlexbox
Blog cards of different lengths whose author and date footer always sits at the bottom edge. Each card is a flex column, and margin-top: auto on the footer pushes it down into the space the shorter cards have spare.
View code
<ul class="cg6"><li><h3>Short title</h3><p>A quick note.</p><footer>Ana · 3 Oct</footer></li><li><h3>A much longer article title that wraps</h3><p>With a longer summary that takes up three lines in the card and pushes things down.</p><footer>Jonas · 1 Oct</footer></li></ul>
A grid of features separated by single-pixel lines, without doubled borders where cards meet. The trick is a 1px gap with the grid's background colour showing through, and each card painted in the page colour on top.
View code
<ul class="cg7"><li><b>Fast</b>Loads in under a second.</li><li><b>Secure</b>Encrypted at rest.</li><li><b>Simple</b>No setup needed.</li><li><b>Open</b>Export any time.</li></ul>
repeat(auto-fill, minmax(min(180px, 100%), 1fr)) picks the column count from the space available and never overflows. It replaces most card grid media queries.
Align the insides
Cards with different text lengths have ragged buttons. Use subgrid to share rows across cards, or make each card a flex column and push its footer down with margin-top: auto.
Lists of cards are lists
Wrap cards in <ul> and <li> so screen readers announce how many there are and people can skip the whole group.
Hairlines without double borders
A 1px gap on a grid with a coloured background, and cards painted in the page colour, gives single dividers everywhere, including where cards meet.
Frequently asked questions
How many cards per row?
Let the grid decide from a minimum card width; three or four on desktop is typical.
Should the whole card be clickable?
If it has one destination, yes, with the title as the link stretched over the card.
Are horizontal scrolling rows OK on desktop?
They hide content, so switch them to a grid when there is room, as the swipe row demo does.