Notes from the field.
-
The questions people ask before they buy a pack.
Research
-
One bar of wax, a hair dryer and an old towel.
Repairs
-
Three ridgelines that look best before sunrise.
Trips
Recent posts as a responsive grid of post cards, with a headline and a link to the full archive.
FeaturesSection1 dependency Original, MIT
The questions people ask before they buy a pack.
Research
One bar of wax, a hair dryer and an old towel.
Repairs
Three ridgelines that look best before sunrise.
Trips
Using Claude Code? Give it the agent prompt and skip these steps.
foundation.css holds the tokens every component reads.
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
--- ---
import BlogGrid from "../components/viewership/blog-grid/BlogGrid.astro";
---
<BlogGrid /> | Prop | Type | Default | Description |
|---|---|---|---|
title | string | · | Headline. |
accent | string | · | Closing words of the headline, drawn in the theme's accent style. |
body | string | · | Short line under the headline. |
posts | PostCard props[] | · | The posts, newest first. Each takes the same props as Post Card. |
all | { label: string; href: string } | · | Link to the archive, beside the headline. |
as | "h1" | "h2" | "h2" | Heading element. Card titles step down one level. |
---
/**
* BlogGrid: a section of recent posts. Headline and an optional "view all"
* link on top, then one to three columns of PostCards depending on the width
* of the container.
*
* @example
* <BlogGrid title="From the" accent="journal." all={{ label: "All posts", href: "/journal" }} posts={posts} />
*/
import PostCard from "../post-card/PostCard.astro";
interface Image {
src: string;
alt: string;
width?: number;
height?: number;
}
interface Post {
href: string;
title: string;
excerpt?: string;
author?: string;
readTime?: string;
tag?: string;
date?: string | Date;
image?: Image;
}
interface Link {
label: string;
href: string;
}
interface Props {
title: string;
accent?: string;
body?: string;
posts: Post[];
/** Link to the full archive, set beside the headline. */
all?: Link;
as?: "h1" | "h2";
class?: string;
}
const { title, accent, body, posts, all, as: Heading = "h2", class: className } = Astro.props;
const cardHeading = Heading === "h1" ? "h2" : "h3";
---
<section class:list={["vs-blog-grid", className]}>
<div class="vs-blog-grid__inner">
<div class="vs-blog-grid__head">
<div>
<Heading class="vs-display vs-blog-grid__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
{body && <p class="vs-blog-grid__body">{body}</p>}
</div>
{
all && (
<a class="vs-blog-grid__all" href={all.href}>
{all.label}
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
</a>
)
}
</div>
<ul class="vs-blog-grid__list">
{
posts.map((p) => (
<li>
<PostCard {...p} as={cardHeading} />
</li>
))
}
</ul>
</div>
</section>
<style>
.vs-blog-grid {
container-type: inline-size;
background: var(--vs-page);
color: var(--vs-ink);
font-family: var(--vs-font-sans);
font-stretch: var(--vs-font-stretch);
}
.vs-blog-grid__inner {
max-width: var(--vs-container);
margin-inline: auto;
padding: var(--vs-section-y) var(--vs-gutter);
}
.vs-blog-grid__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px 32px;
margin-bottom: clamp(28px, 5cqi, 48px);
}
.vs-blog-grid__title {
font-size: clamp(2rem, 5.5cqi, 3.5rem);
}
.vs-blog-grid__body {
max-width: 32rem;
margin: 1rem 0 0;
font-size: 1.0625rem;
line-height: 1.55;
color: var(--vs-ink-2);
}
.vs-blog-grid__all {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 0;
color: var(--vs-ink);
font-size: 0.9375rem;
font-weight: 500;
text-decoration: none;
border-radius: var(--vs-radius-chip);
}
.vs-blog-grid__all:focus-visible {
outline: none;
box-shadow: var(--vs-focus-ring);
}
.vs-blog-grid__all svg {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
transition: transform var(--vs-duration) var(--vs-ease-out);
}
.vs-blog-grid__all:hover svg {
transform: translateX(3px);
}
.vs-blog-grid__list {
display: grid;
gap: 20px;
margin: 0;
padding: 0;
list-style: none;
}
.vs-blog-grid__list > li {
display: grid;
}
@container (min-width: 620px) {
.vs-blog-grid__list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@container (min-width: 960px) {
.vs-blog-grid__list {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 24px;
}
}
</style> Original, MIT.