
A single-post layout with centered title, subtitle, author row with avatar and site link, hero media, and prose body including headings, alert, table, and figures.
@shadcnblocks
block/blogpost1/blogpost1.tsx
import {
Facebook,
Home,
Link2,
Linkedin,
Twitter,
} from "lucide-react";
import { cn } from "cn";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
interface BlogPostAuthor {
name: string;
role?: string;
image?: Image;
href?: string;
website?: string;
websiteName?: string;
bio?: string;
}
interface Image {
src: string;
alt: string;
srcDark?: string;
}
interface BlogPostArticleProps {
title: string;
description?: string;
date?: string;
datePrefix?: string;
image?: Image;
body?: React.ReactNode;
author?: BlogPostAuthor;
className?: string;
}
interface Blogpost1Props extends BlogPostArticleProps {}
type Props = Partial<Blogpost1Props>;
const person1 = {
name: "Jack Walsh",
firstName: "Jack",
lastName: "Walsh",
initials: "JW",
email: "jack.walsh@example.com",
username: "jackwalsh",
role: "Product Designer",
company: "Cartwright",
location: "Brisbane, Australia",
avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar1.webp",
};
const modernBlogPostArticle: BlogPostArticleProps = {
title: "Building the perfect blog post with shadcn/ui",
description:
"I rebuilt my blog on shadcn/ui expecting a weekend project. What I got was a better way to think about components, layouts, and the code I actually own.",
lead: [
"I have rebuilt my blog four times in six years. Every time, the UI library was the part I ended up fighting.",
"This time I used shadcn/ui, and for the first time the components felt like mine instead of something I had rented.",
"Here is what changed, what surprised me, and the few things I would do differently if I started again tomorrow.",
"If you have ever overridden a library style with !important at 1am, this one is for you.",
],
category: "Development",
date: "March 18, 2026",
datePrefix: "Published on",
readTime: "8 min read",
image: {
src: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/blog/photo-1-16x9.jpg",
alt: "A white building with vertical fins against a clear sky",
},
contentImage: {
src: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/blog/photo-4-16x9.jpg",
alt: "Curved concrete balconies on a building against a blue sky",
},
body: (
<>
<h2 id="section1" className="scroll-mt-24">
I was tired of fighting my UI library
</h2>
<p>
I have rebuilt my blog four times in six years. Each time I picked a
popular component library, and each time I ended up in the same place,
rebuilding many components from scratch.
</p>
<p>
So when people started talking about <a href="#">shadcn/ui</a>, I was
skeptical. Another library? But it is <strong>not a library</strong>.
You run a command, and the source code for a button lands in your
project. Not a package. Not a wrapper. <em>The actual file.</em>
</p>
<p>
That sounds like a small detail. It is not. It changes who is in charge
of your UI, and it took me a few weeks to realize how much I had been
designing around other people's decisions.
</p>
<h3>What owning the code actually means</h3>
<p>
With a traditional library, customization is a negotiation. You read the
docs, find the right prop, and hope the maintainers anticipated your use
case. With shadcn/ui, you open the file and change it.
</p>
<ul>
<li>
<strong>No version lock-in.</strong> Upgrades are opt-in, one
component at a time.
</li>
<li>
<strong>No theme API to learn.</strong> Styling is plain{" "}
<a href="#">Tailwind CSS</a> and CSS variables.
</li>
<li>
<strong>Accessibility built in.</strong> Under the hood:
<ul>
<li>Radix UI or Base UI handle focus and keyboard behavior</li>
<li>ARIA attributes are already wired up</li>
</ul>
</li>
</ul>
<p>
I used to think the hard part of a UI library was the components.{" "}
<strong>I was wrong</strong>. The hard part was finding the right prop
accepting that I could just edit the file.
</p>
<blockquote>
<p>
The best component library is the one you can delete a line from
without asking anyone for permission.
</p>
</blockquote>
<h2 id="section2" className="scroll-mt-24">
Rebuilding the post page from scratch
</h2>
<p>
The page I cared about most was the article itself. The one you are
reading right now. I wanted a calm reading column, a sticky table of
contents, and typography that did not need babysitting.
</p>
<h3>Getting set up</h3>
<ol>
<li>
Initialize the project with <code>npx shadcn@latest init</code>.
</li>
<li>Add the handful of components the page actually needs.</li>
<li>
Pull in a starting layout from <a href="#">Shadcnblocks</a> instead of
a blank file.
</li>
</ol>
<pre>
<code>{`npx shadcn@latest add avatar badge separator
# Added
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/separator.tsx`}</code>
</pre>
<p>
Three components, three files, all of them readable in under a minute. I
opened <code>badge.tsx</code>, tweaked the radius, and moved on. No
config file, no theme provider, no <abbr title="Pull request">PR</abbr>{" "}
to someone else's repo.
</p>
<p>
Small quality-of-life win: the docs have a command menu. Press{" "}
<kbd>⌘</kbd> + <kbd>K</kbd>, type a component name, and you are there.
</p>
<table>
<thead>
<tr>
<th>What changed</th>
<th>Old blog</th>
<th>shadcn/ui rebuild</th>
</tr>
</thead>
<tbody>
<tr>
<td>UI dependencies</td>
<td>1 large package</td>
<td>Copied source files</td>
</tr>
<tr>
<td>Style overrides</td>
<td>Dozens</td>
<td>None</td>
</tr>
<tr>
<td>Dark mode</td>
<td>A plugin and a prayer</td>
<td>CSS variables</td>
</tr>
<tr>
<td>Time to first post</td>
<td>Two weekends</td>
<td>One afternoon</td>
</tr>
</tbody>
</table>
<h2>Good component systems work the same way</h2>
<p>
Do not install every component on day one. Add them as you need them.
The whole point is a codebase you understand, and that is easier when it
grows with the project.<sup>1</sup>
</p>
<figure>
<img
src="https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/blog/photo-4-16x9.jpg"
alt="Curved concrete balconies on a building against a blue sky"
className="aspect-video w-full rounded-md object-cover"
/>
<figcaption>
Repeated parts, one coherent building. Good component systems work the
same way.
</figcaption>
</figure>
<p>
A few terms kept coming up while I worked, so here is how I ended up
thinking about them:
</p>
<p>
Do not treat the defaults as sacred either. They are a starting point,
not a spec. Change things. That is the deal you signed up for.
</p>
<p>
And if you want to skip the blank page entirely, start from a finished
layout and edit it down. That is what I did with this one. If you build
something with it, I would love to see it. Find me on <a href="#">X</a>{" "}
or drop a link in the <a href="#">comments</a>.
</p>
<p>
<small>
<sup>1</sup> I installed every component on day one. Learn from my
mistakes.
</small>
</p>
</>
),
author: {
name: person1.name,
role: person1.role,
href: "#",
image: {
src: person1.avatar,
alt: person1.name,
},
bio: `${person1.firstName} writes about shadcn/ui, front-end craft, and rebuilding the same blog more times than is reasonable.`,
},
breadcrumbs: [
{ label: "Home", url: "/", icon: Home },
{ label: "Blog", url: "/" },
{ label: "Development" },
],
sectionsLabel: "On this page",
sections: [
{ id: "section1", label: "I was tired of fighting my UI library" },
{ id: "section2", label: "Rebuilding the post page from scratch" },
{ id: "section3", label: "What I would tell you before you start" },
],
shareLabel: "Share this article",
shareLinks: [
{ label: "Twitter", url: "#", icon: Twitter },
{ label: "LinkedIn", url: "#", icon: Linkedin },
{ label: "Facebook", url: "#", icon: Facebook },
{ label: "Copy link", url: "#", icon: Link2 },
],
};
const defaultProps: Blogpost1Props = {
...modernBlogPostArticle,
author: {
...modernBlogPostArticle.author,
name: modernBlogPostArticle.author?.name ?? "",
website: "https://www.shadcnblocks.com",
websiteName: "Shadcnblocks",
},
};
const Blogpost1 = (props: Props) => {
const {
className,
title,
description,
author,
image,
body,
date,
datePrefix,
} = {
...defaultProps,
...props,
};
return (
<section className={cn("py-32", className)}>
<div className="container">
<div className="mx-auto flex max-w-5xl flex-col items-center gap-4 text-center">
<h1 className="max-w-3xl text-5xl font-semibold text-pretty md:text-6xl">
{title}
</h1>
<h3 className="max-w-3xl text-lg text-muted-foreground md:text-xl">
{description}
</h3>
<div className="flex flex-col items-center gap-1 text-sm md:flex-row md:gap-2 md:text-base">
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8 border">
<AvatarImage
src={author?.image?.src}
alt={author?.image?.alt}
/>
<AvatarFallback>
{author?.name
.split(" ")
.map((part) => part[0])
.join("")}
</AvatarFallback>
</Avatar>
<span className="font-semibold">{author?.name}</span>
</div>
{author?.website && author.websiteName ? (
<span className="text-muted-foreground">
Owner of{" "}
<a
href={author.website}
className="font-semibold text-foreground hover:underline"
>
{author.websiteName}
</a>
</span>
) : null}
<span className="text-muted-foreground">
{datePrefix ? `${datePrefix} ` : null}
{date}
</span>
</div>
<img
src={image?.src}
alt={image?.alt}
className="mt-4 mb-8 aspect-video w-full rounded-lg border object-cover"
/>
</div>
</div>
<div className="container">
<div className="mx-auto prose max-w-3xl dark:prose-invert">{body}</div>
</div>
</section>
);
};
export { Blogpost1 };





























