GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

CSS Card Design Examples — 9 Content Card Types

CSS card design is the craft of building containers that lay out content. The rule across all nine: put the click on the link, not the whole card.

Auto-plays · click a tile to jump to its section · all nine in one zip

The order is not popularity but how content becomes a card. One card becomes several (01, 02), cards turn into signposts (03), then a single card splits internally (04, 05, 06), mood lands on top (07), cards assemble into one block (08), and finally a large set keeps its reading order intact (09). The spine of this piece is not effects but layout and links — the principle of putting the click on the link, not the whole card, runs through all nine. For the same lineage see CSS button hover effects and CSS-only tab menus.

01City guide grid card

Three city cards with a name and one-line blurb line up through grid-template-columns, and each label band rises into place on its own turn — a travel or local-service section card.

City guide grid card
Open in a new tab

01 · City guide grid card — Measured area 34.122% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-template-columnsanimation-delayanimation-fill-mode

The grid sets the three columns, the delay sets the entrance

.cg {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: $sp-2;
  box-sizing: border-box; width: min(100%, 440px);
  margin: 0; padding: 0; list-style: none;
}

Because the three columns are pinned first instead of sizing each card, growing to four cards means changing repeat(3, 1fr) to repeat(4, 1fr) and nothing inside the cards.

Delayed entrances need fill-mode beside them

// keep the pre-rise state visible during the delay — otherwise frame 0 flashes the finished card
.cg.is-demo .cg__card { animation-fill-mode: backwards; }
.cg.is-demo .cg__card:nth-child(2) { animation-delay: .12s; }
.cg.is-demo .cg__card:nth-child(3) { animation-delay: .24s; }

A card staggered by animation-delay should show its "before rising" state through the delay, because without backwards the finished state shows, disappears, and rises again.

02Team member card

A contact row hidden under the name and role unfolds with clip-path when the card takes focus, and the link itself carries the person's name — an about-us team section card.

Team member card
Open in a new tab

02 · Team member card — Measured area 1.531% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

clip-pathfocus-withintransition

Hold the space, change only what is visible

.tm__contact {
  display: flex; align-items: center; gap: $sp-1;
  width: 100%; margin: 0; padding-top: $sp-1;
  border-top: 1px solid $edge;
  clip-path: inset(0 0 100% 0);
  transition: clip-path $duration $easing;
}
.tm__card:hover .tm__contact,
.tm__card:focus-within .tm__contact { clip-path: inset(0 0 0 0); }

The contact row holds its height from the start and hides only its lower edge with clip-path, so neighbours never shift when it unfolds, and the layout is never recomputed when it collapses again.

Open on hover and on focus alike

:focus-within is set alongside the hover rule, so a keyboard-only visitor reaching the link sees the same expanded state — pairing every hover interaction with its focus twin is this piece's standing rule.

03Shortcut tile card

Four square tiles keep their shape with aspect-ratio while an arrow slides toward the corner to say where the tile leads — a home-screen quick menu card.

Shortcut tile card
Open in a new tab

03 · Shortcut tile card — Measured area 1.637% · moving frames 19/23Original by GODRICH · SCSS and React ship in the zip · See the password

aspect-ratiotranslategrid

A square is a ratio, not a height

.st__tile {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto 1fr auto;
  aspect-ratio: 1 / 1;
  box-sizing: border-box;
  padding: $sp-2;
  border-radius: $r-card;
  background: $subject-cream;
}

Because the height is pinned with aspect-ratio: 1 / 1 instead of a fixed pixel value, the tiles stay square when four columns collapse to two on a narrow screen, and the only thing that grows is label wrapping.

The arrow moves on the translate property

.st__go {
  translate: 0 0;
  transition: translate $duration $easing;
}
.st__tile:hover .st__go,
.st__tile:focus-within .st__go { translate: $reach (-$reach); }

Using the standalone translate property instead of the transform shorthand means later effects can add scale or rotate without overwriting anything, and transition watches exactly one property.

04Tabbed about-me card

Tap intro, career, or contact and the panel swaps in one frame while the underline slides to that tab on radio inputs alone — a personal about page card.

Tabbed about-me card
Open in a new tab

04 · Tabbed about-me card — Measured area 5.933% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

steps(1, end)hastranslate

Radios hold the state, the card reads it with :has()

.ab__ink {
  position: absolute; left: 0; bottom: -1px;
  width: $tab-w; height: 3px; border-radius: $r-pill;
  background: $color;
  translate: 0 0;
  transition: translate $duration $easing;
}
.ab:has(#abT2:checked) .ab__ink { translate: $step 0; }
.ab:has(#abT3:checked) .ab__ink { translate: $step * 2 0; }

The radio stores the state and the card reads it with :has() to move the underline and swap the panel — one mechanism keeping both in sync with no JS.

The underline width equals the measured tab width

The underline moves in fixed pixels, so each tab is a fixed-width cell (flex: 0 0 auto, 72px) — the width was measured with getBoundingClientRect, not guessed, so longer labels in other languages never shift the slots.

05Pinboard review card

Review cards of different heights flow into place through column-count, settling one at a time into a wall with no gaps — a reviews page card.

Pinboard review card
Open in a new tab

05 · Pinboard review card — Measured area 28.106% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · See the password

column-countbreak-insideanimation-delay

The one line that stops cards being sliced

.pin {
  box-sizing: border-box; width: 100%;
  column-count: 3;
  column-gap: $sp-3;
}
.pin__card {
  // keep the card whole across column breaks — the one line that matters in multicolumn
  break-inside: avoid;
  box-sizing: border-box;
  margin-bottom: $sp-3;
  padding: $sp-2;
  border-left: 3px solid $color;
  background: $card-bg;
}

Multicolumn flows lines from column to column, so an unguarded review card gets sliced in half at the bottom edge — break-inside: avoid pins "do not split this box" and the whole card jumps to the next column.

No gaps means no gaps between cards, inside a column

The wall is gap-free between cards within each column; below the last card the column ends naturally — forcing columns to fill to their bottom edge stretches or clips cards, and a layout that must fill the grid belongs to 09's grid placement instead.

06Growing spec card

One product card opens from grid-template-rows 0fr to 1fr so the hidden spec table pushes down while its neighbours stay put — a product comparison list card.

Growing spec card
Open in a new tab

06 · Growing spec card — Measured area 5.152% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-template-rowsminmaxoverflow

Grow the track instead of guessing a max-height

.sg__grow {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows $duration $easing;
}
// the two lines that actually collapse a 0fr track — without them the table stays visible
.sg__inner { overflow: hidden; min-height: 0; }
.sg__card.is-open .sg__grow { grid-template-rows: 1fr; }

Opening with max-height means guessing a "big enough" value that clips longer content, while the 0fr → 1fr transition grows exactly to the content's real height with no guesswork.

Two collapse lines, and why neighbours hold still

A 0fr track's child keeps its own size, so without overflow: hidden + min-height: 0 the table spills out of the collapsed track — and unless the outer grid is align-items: start instead of the default stretch, one growing card drags the whole row taller.

07Mood-first event card

A large date fills one side of the card, the title line rises from below, and a tinted band tilts slowly to set the mood of the event — a gig or exhibition schedule card.

Mood-first event card
Open in a new tab

07 · Mood-first event card — Measured area 7.618% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

rotatemix-blend-modeanimation-delay

The band is a multiplied color, not an image

.em__card {
  position: relative;
  // the card becomes its own compositing group — without this the band multiplies into the orange stage
  isolation: isolate;
  overflow: hidden;
}
.em__mood {
  mix-blend-mode: multiply;
  rotate: -#{$tilt};
}

The band is the card face multiplied by a color via mix-blend-mode: multiply, so changing the card background retints the band — and without isolation: isolate the multiplication punches through the card into the orange stage behind it.

Text contrast survives the band passing over

When the band passes, the face shifts from cream to the multiplied color, and even the darkest combination (cream × orange = #ff4b1c) keeps ink text at 5.50:1 — so all text is one ink color, and hierarchy comes from weight and size rather than faded alpha.

08Intro grid section card

One large card and four small ones take named grid-area slots, the big title appears first, and the small cards follow on a stagger — a service overview above the fold.

Intro grid section card
Open in a new tab

08 · Intro grid section card — Measured area 18.81% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-areagrid-template-areasanimation-delay

Draw the blueprint first, tag the cards second

.ig {
  display: grid;
  grid-template-columns: 1.25fr 1fr 1fr;
  grid-template-areas:
    "hero a b"
    "hero c d";
  grid-template-rows: repeat(2, 1fr);
  gap: $sp-2;
}
// name tags — a card reaches its slot wherever it sits in the HTML
.ig__hero { grid-area: hero; }
.ig__cell--a { grid-area: a; }

grid-template-areas is the blueprint and grid-area is each card's name tag, so redrawing the narrow-screen blueprint line by line rearranges everything without touching card order (HTML) or classes.

Big card first, small cards on a stagger

.ig.is-demo .ig__title { animation-name: igTitle; animation-fill-mode: backwards; }
.ig.is-demo .ig__cell--a { animation-delay: .14s; }
.ig.is-demo .ig__cell--b { animation-delay: .24s; }
.ig.is-demo .ig__cell--c { animation-delay: .34s; }
.ig.is-demo .ig__cell--d { animation-delay: .44s; }

Where the eye should land (the big card's title) appears first and supporting detail follows on a stagger — every staggered element needs animation-fill-mode: backwards beside it so the finished state never flashes during the delay.

09Reading-order waterfall

Cards of unequal height fill gaps through grid-auto-flow dense while keeping their numbered order, so the visual order matches the source order — a blog index card.

Reading-order waterfall
Open in a new tab

09 · Reading-order waterfall — Measured area 39.506% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-auto-flowgrid-roworder

Three properties, three different jobs

.wf {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 46px;
  // backtrack to fill leftover cells — must pair with the grid-row lines below to keep order
  grid-auto-flow: row dense;
  gap: $sp-2;
}
.wf__card--s2 { grid-row: 1 / span 2; grid-column: 2; }
.wf__card--tall { grid-row: span 2; }
.wf__card--pin { order: -1; }

dense makes auto-placement backtrack to fill leftover cells, but the filler is always a later card — pinning the tall cards' rows with grid-row placement means the auto-flowing cards land in numbered order with no leftover cells to backtrack into in the first place.

The pinned post moves forward on order alone

The pinned post (number 1) sits last in the HTML, and a single order: -1 moves only its placement to the front — the markup stays untouched while the on-screen order changes.

What do css card design examples decide first?

There is a fixed order of decisions when building a card. First the column count and widths (grid-template-columns), then the card face (background, radius, border), then the owner of the link (the whole card, or a title link). Reverse it and the earlier decisions collapse — make the whole card clickable first, and the moment you add a second link inside it the click areas overlap.

All nine here chose columns first. 01 pins repeat(3, 1fr), 03 pins a square aspect-ratio, 08 draws a grid-template-areas blueprint, 09 lays out with dense — once placement stands, the effects inside (label bands, contact rows, tinted bands) play inside a frame that never shakes. The answer is the same if you came looking for css card effects or css card animations: an effect rides on top of layout and never substitutes for it.

The link-owner principle runs through the whole piece as well. Putting the click on the link, not the whole card, means the link text alone says where it goes (02's "Email Seoyeon", 03's "Orders", 08's "Take a tour"). Screen readers walk links one by one rather than reading a card as a single blob, so a link that names its own destination does half the accessibility work.

Where it breaks — dense reorders, 0fr spills

dense alone breaks the reading order

This is the trap measured while building 09. With only grid-auto-flow: row dense and no pinned rows, card 2 gets pushed to the next line by its two-row span and the gap backfills with card 3 — the eye follows the numbers while the screen shows 1, 3, 4, 2. The explicit placement (grid-row: 1 / span 2) must ride along so no backtrackable gap exists in the first place. The authoritative rules live in the MDN grid-auto-flow page.

A 0fr track does not collapse on its own

In 06 the spec table started out visible when it should have been hidden. grid-template-rows: 0fr zeroes the track height but never the child's own size; adding overflow: hidden and min-height: 0 on the child pulled the table into the track. Without those two lines, a 0fr track is not collapsed — its content is stacked and spilling.

A centered card block jumps every time it opens

One more from 06. With the stage centering via place-items: center, the moment the board grew all three cards jumped upward — the instant the card's claim of "neighbours stay put" turned false on screen. Stretching the board to the stage height (align-self: stretch) and stacking rows from the top (align-content: start) left exactly one growing cell; the measured board position change across open and close came out to 0px.

The nine demos and this full write-up are bundled with a password. The HTML, SCSS, and React versions of all nine cards plus the complete article unlock with the password 75rkpw3y, and everything you saw in the grid arrives as files ready to paste. The three traps above are reproducible inside the zip's sources — delete the grid-row lines in 09 and the order breaks, delete the overflow: hidden pair in 06 and the table spills.

Accessibility — what remains when motion is removed

All nine turn their animations off under prefers-reduced-motion: reduce and the cards stay exactly in place. 01's label bands show already risen, 02's contact row still unfolds on hover with only the clip transition cut, 04's underline jumps as a cut while the panel swap was already a cut, and 05's stagger disappears with every review visible from the start. No card relies on motion as the only carrier of information — a still frame of any card reads who goes where. Focus rings are set separately with :focus-visible on all nine, and the contrast of 07's text under its passing band (5.50:1) was already computed above.

FAQ

Can css card design examples be copied as-is?

Yes. Every snippet above is written to be pasted directly. Only the on-screen copy — tile labels in 03, event names in 07 — is Korean, so swap in your own text and replace the chips with the icon set you use.

My card heights are all different — how do css card design ideas handle that?

The first move is not to equalize but to let them differ well: 05 flows uneven heights through multicolumn, and 09 fills gaps with dense placement. When they must line up, 08 pins row heights with grid-template-rows and 01 divides evenly with 1fr columns.

Does grid-template-areas work in older browsers?

grid-template-areas and grid-area work in every browser supporting grid, which is everything since 2017. The comparatively new properties here are :has() in 04 (all major browsers since 2023) and aspect-ratio in 03 (2021) — check those two against your target browsers.

Enter the archive password

The password is inside this article. You will find it as you read.