This post exercises every custom component available in the blog renderer.
All of these can be dropped directly into any .mdx file.
Callout
Four variants — useful for surfacing key information inline.
This is a note — general context or background info.
This is a tip — a shortcut or best practice worth highlighting.
This is a warning — something that could go wrong if you're not careful.
This is important — a critical detail that must not be missed.
You can also trigger callout variants using plain GitHub-style blockquote syntax — no JSX needed:
Works in regular markdown too, no component import required.
CodeFile
Named code blocks — great for tutorial posts where you're showing exactly which file to edit.
import { getAllPostsMeta } from "@/lib/content/blog";
export default function BlogPage() {
const posts = getAllPostsMeta();
return <BlogGrid posts={posts} />;
}
import Link from "next/link";
type PostPreviewCardProps = {
title: string;
slug: string;
tags: string[];
};
export function PostPreviewCard({ title, slug, tags }: PostPreviewCardProps) {
return (
<article className="rounded-lg border p-4">
<Link href={`/blog/${slug}`} className="font-semibold">
{title}
</Link>
<p className="mt-2 text-sm text-muted-foreground">
{tags.join(" / ")}
</p>
</article>
);
}
Tabs
Composable Tabs — drop them straight into MDX, no wrapper component. The default TabsList renders a segmented pill control; pass variant="line" for the minimal underline treatment.
The same content with the line variant:
CodeDropdown
When there are too many variants for a tab row — or the labels are long — use adjacent fenced code blocks with matching group metadata. The compiler turns them into a dropdown selector.
{
"scripts": {
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"gray-matter": "^4.0.3",
"rehype-highlight": "^7.0.2"
}
}
Steps
Step-by-step tutorials with a numbered connector line.
Install dependencies
Add the packages needed for the blog feature:
npm install gray-matter next-mdx-remote remark-gfm rehype-highlight
Create your first post
Add an .mdx file to content/blog/. The filename becomes the URL slug.
touch content/blog/my-first-post.mdx
Add frontmatter
Every post needs a title, date, excerpt, and tags:
---
title: "My First Post"
date: "2026-03-08"
excerpt: "A short summary of the post."
tags: ["web dev", "meta"]
---
Start writing
Below the frontmatter, write regular Markdown or JSX — any of the components on this page are available immediately, no imports needed.
Kbd
Inline keyboard shortcuts with keycap styling.
Open the command palette with ⌘K (Mac) or CtrlK (Windows/Linux).
Save a file: ⌘S. Search globally: ⌘ShiftF.
FileTree
Visual directory structure — essential for "here's how the project is laid out" sections.
Mermaid
Rendered Mermaid diagrams with the same compact shell used by the blog's code and file components.
Timeline
Changelog or history sections with a clean vertical connector.
Blog redesign
Rewrote all in-blog MDX components from scratch. New additions: Timeline and PostImage.
GitHub snapshot
Added a real-time GitHub activity section to the homepage, powered by the GitHub REST and GraphQL APIs.
Travel page
Built an interactive world map with react-simple-maps showing every
country visited.
Initial launch
First version of the portfolio went live with hero, projects grid, and contact form.
PostImage
Styled images with optional caption and photo credit.
ToggleList
Collapsible sections for hiding/showing detailed content — great for FAQs, advanced options, or supplementary info.
The ToggleList component is perfect for hiding verbose content behind collapsible headers. Users can expand sections that interest them without being overwhelmed by information.
Wrap multiple ToggleItem components inside ToggleList. Each item has a title prop and accepts any JSX content as children.
<ToggleList>
<ToggleItem title="First item">Content here</ToggleItem>
<ToggleItem title="Second item">More content</ToggleItem>
</ToggleList>
Yes! Add the defaultOpen={true} prop to any ToggleItem to have it expanded by default. Like this one.
Anything! Paragraphs, lists, code blocks, images — all standard Markdown and MDX components work inside ToggleItem children.
- Bullet point one
- Bullet point two
- Bullet point three
Combining components
Components compose naturally. Here's a full mini-tutorial using several at once:
Adding a new page
Follow these steps any time you add a top-level route to the site.
Add the route to site.config.ts
The nav array drives both the desktop header and the mobile sheet automatically.
nav: [
{ label: "Projects", href: "/projects" },
{ label: "Blog", href: "/blog" }, // ← add this
{ label: "Travel", href: "/travel" },
],
Create the page file
Next.js App Router — just drop a page.tsx in the right folder:
mkdir -p app/blog && touch app/blog/page.tsx
Add a command palette entry
Open components/site/command-palette.tsx and add a CommandItem
with the appropriate lucide icon. Press ⌘K to test it.
