Designing with cascade layers
Why layer order beats specificity wars, and how mCSS hands the final word to your unlayered CSS.
The .articleCard component is the blog-index pattern: image, tags, title, description, and a byline with avatar and date. .postList lays the cards out in equal-height responsive columns; this siteβs own blog index is built from exactly these two components.
Why layer order beats specificity wars, and how mCSS hands the final word to your unlayered CSS.
One file of custom properties is the whole theming story.
| File | Description | Source |
|---|---|---|
component.articleCard.css | ArticleCard + PostList styles | Github |
component.card.css | Base card visuals (dependency) | Github |
ArticleCard.astro, PostList.astro | The Astro components | Github |
Tags.astro, Avatar.astro | Composed components (dependencies) | Github |
--articleCard-* block in default theme | Theme token (see below) | Github |
ArticleCard extends the card (same border, radius, tokens) and composes Tags and Avatar; PostList is a ul over the grid system. The card carries one link β the title, stretched over the whole card with a ::after β so screen readers hear one link named by the title, and the tags stay independently clickable above it.
<ul class="postList grid" col="1" col-md="2" col-lg="2"> <li> <article class="card articleCard"> <figure class="articleCard_media"> <img class="articleCard_image" src="cover.jpg" alt="" loading="lazy" /> </figure> <nav aria-label="Tags"><ul class="tags tags-inline articleCard_tags"> <li><a href="/blog/tags/css">css</a></li> </ul></nav> <h3 class="articleCard_title"><a href="/blog/my-post">Designing with cascade layers</a></h3> <div class="articleCard_description"><p>Why layer order beats specificity wars.</p></div> <footer class="articleCard_byline"> <!-- avatar markup, see the Avatar docs --> <div> <span class="articleCard_author">Sonny Mueller</span> <time datetime="2026-05-02">May 2, 2026</time> </div> </footer> </article> </li></ul>| Property | Description |
|---|---|
--articleCard-image-aspect-ratio | Cover image ratio (defaults to --ar-golden). |
Everything else (borders, radius, colors, spacing) comes from the card tokens.
ArticleCard
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | β | Post URL. Required. |
title | string | β | Post title (the cardβs single link). Required. |
imageSrc | ImageMetadata | string | undefined | Cover image (imported asset or URL/path). |
imageAlt | string | "" | Cover image alt text. |
tags | string[] | [] | Tag names, rendered with the Tags component. |
tagsUrl | string | "/blog/tags" | Base URL for tag links. |
author | string | undefined | Byline name (also feeds the Avatar initials). |
avatarSrc | ImageMetadata | string | undefined | Byline avatar image. |
date | Date | undefined | Publication date, rendered as a <time>. |
locale | string | "en-US" | Locale for the formatted date. |
variant | string | undefined | Card variant pass-through: filled, raised. |
class | string | undefined | Additional CSS classes. |
The default slot is the description (rich HTML welcome).
PostList
| Prop | Type | Default | Description |
|---|---|---|---|
cols | number | 2 | Columns from --lg: 1, 2, or 3. |
Children go in the default slot; wrap each card in an <li>.
Only href and title are required; everything else is optional.
---import ArticleCard from "../components/ArticleCard.astro";---<ArticleCard href="/blog/my-post" title="A minimal card" variant="filled"> <p>Only `href` and `title` are required.</p></ArticleCard>