# Blog 1

> Three-column blog grid with image placeholders and category metadata.

A shadcn/ui block from the [@nusaiba](https://nusaiba.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nusaiba/blog-1).

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/blog-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/blog-1-light-4d90b94caa3edf0b.png
- Registry item: https://nusaiba.dev/r/base-nova/blog-1.json
- Categories: blog
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/blog-1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `@nusaiba/foundation`, `@nusaiba/gallery-visuals`

## Files

### `registry/blocks/blog/1/blog-section.tsx`

```tsx
"use client";

import Image from "next/image";
import Link from "next/link";
import { ArrowRightIcon } from "lucide-react";
import { motion } from "motion/react";
import { SectionShell, ContentRail } from "@/components/layout-contract";

const posts = [
	{
		title: "How we cut release cycles in half",
		excerpt: "A practical playbook for smaller teams shipping on tight deadlines.",
		category: "Product",
		date: "Mar 12, 2026",
		image: "/images/visuals/floating-lines.png",
		alt: "Flowing white light threads on a black background",
	},
	{
		title: "Designing onboarding that converts",
		excerpt: "Five patterns we use to activate users in the first session.",
		category: "Design",
		date: "Mar 4, 2026",
		image: "/images/visuals/silk.png",
		alt: "Dark silk fabric folds with soft highlights",
	},
	{
		title: "What SOC 2 taught our engineering team",
		excerpt: "Security controls that actually improved how we build software.",
		category: "Engineering",
		date: "Feb 22, 2026",
		image: "/images/visuals/dark-veil.png",
		alt: "Purple light veil on a dark background",
	},
];

export function BlogSection() {
	return (
		<SectionShell spacingMode="section">
			<ContentRail maxWidth="max-w-5xl" className="space-y-10">
				<div className="flex flex-wrap items-end justify-between gap-4">
					<div>
						<h2 className="font-medium text-2xl tracking-tight md:text-4xl">
							Latest from the blog
						</h2>
						<p className="mt-3 max-w-lg text-muted-foreground text-sm md:text-base">
							Product updates, team stories, and lessons from building in public.
						</p>
					</div>
					<Link
						className="inline-flex items-center gap-1.5 font-medium text-sm hover:underline"
						href="#"
					>
						View all posts
						<ArrowRightIcon className="size-3.5" />
					</Link>
				</div>

				<div className="grid gap-8 md:grid-cols-3">
					{posts.map((post, index) => (
						<motion.article
							className="group"
							initial={{ opacity: 0, y: 20 }}
							key={post.title}
							transition={{ delay: index * 0.08, duration: 0.45 }}
							viewport={{ once: true }}
							whileInView={{ opacity: 1, y: 0 }}
						>
							<div className="relative aspect-[4/3] overflow-hidden rounded-[var(--radius-md)] border border-border bg-muted/40">
								<Image
									alt={post.alt}
									className="object-cover transition-transform duration-500 group-hover:scale-[1.03]"
									fill
									sizes="(max-width: 768px) 100vw, 33vw"
									src={post.image}
								/>
							</div>
							<div className="mt-4 flex items-center gap-2 text-muted-foreground text-xs">
								<span>{post.category}</span>
								<span>·</span>
								<span>{post.date}</span>
							</div>
							<h3 className="mt-2 font-medium text-base leading-snug md:text-lg">
								<Link className="hover:underline" href="#">
									{post.title}
								</Link>
							</h3>
							<p className="mt-2 text-muted-foreground text-sm leading-relaxed">
								{post.excerpt}
							</p>
						</motion.article>
					))}
				</div>
			</ContentRail>
		</SectionShell>
	);
}
```
