# Blogpost 1 - Centered article with alert and table

> 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.

A shadcn/ui block from the [@shadcnblocks](https://shadcnblocks.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnblocks/blogpost1).

- Page, with a live preview: https://shadcn.exchange/r/shadcnblocks/blogpost1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnblocks/blogpost1-light-df531c2d204aa936.png
- Registry item: https://shadcnblocks.com/r/blogpost1.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnblocks.com/r/blogpost1.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: `cn`, `lucide-react`
- Registry items: `avatar`

## Files

### `block/blogpost1/blogpost1.tsx`

```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&apos;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&apos;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 };
```
