
A two-column blog article with a sticky narrow column for back navigation, display title, and avatar byline beside a main column with hero image and full prose.
@shadcnblocks
block/blogpost2/blogpost2.tsx
import {
ChevronLeft,
Facebook,
Home,
Link2,
Linkedin,
Twitter,
} from "lucide-react";
import { cn } from "cn";
import { Avatar, 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;
date?: string;
image?: Image;
body?: React.ReactNode;
author?: BlogPostAuthor;
className?: string;
}
interface Blogpost2Props extends BlogPostArticleProps {
backLink?: {
text: string;
url: string;
};
}
type Props = Partial<Blogpost2Props>;
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 defaultProps: BlogPostArticleProps = {
title: "Building the perfect blog post with shadcn/ui",
date: "March 18, 2026",
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",
},
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.`,
},
};
const Blogpost2 = (props: Props) => {
const { className, title, author, image, date, backLink, body } = {
...defaultProps,
...props,
};
return (
<section className={cn("py-32", className)}>
<div className="container">
<div className="relative flex flex-col justify-between gap-10 lg:flex-row">
<aside className="top-10 h-fit flex-shrink-0 lg:sticky lg:w-[300px] xl:w-[400px]">
<a
className="mb-5 flex items-center gap-1 text-muted-foreground hover:text-primary"
href={backLink?.url}
>
<ChevronLeft className="h-full w-4" />
{backLink?.text}
</a>
<h1 className="mb-5 text-3xl font-bold text-balance lg:text-4xl">
{title}
</h1>
<div className="flex gap-3">
<Avatar className="size-7 rounded-full">
<AvatarImage
src={author?.image?.src}
alt={author?.image?.alt}
/>
</Avatar>
<div>
<h2 className="font-semibold">{author?.name}</h2>
<p className="text-xs text-muted-foreground">{date}</p>
</div>
</div>
</aside>
<article className="">
<img
src={image?.src}
alt={image?.alt}
className="mt-0 mb-8 aspect-video w-full rounded-lg object-cover"
/>
<div className="prose dark:prose-invert">{body}</div>
</article>
</div>
</div>
</section>
);
};
export { Blogpost2 };



























