Customization

Last updated on 2026-03-22

The Blog CMS Kit is designed to be customized for your blog or publication. All components use design tokens and Tailwind CSS utilities, making brand adaptation straightforward.

Changing Colors

Update CSS custom properties in app/globals.css. The default amber theme uses oklch hue 55. Change the hue to rebrand the entire kit:

:root {
  /* Change from amber (hue 55) to blue (hue 250) */
  --primary: oklch(0.45 0.2 250);
  --primary-foreground: oklch(0.98 0 0);
  --accent: oklch(0.94 0.02 250);
  --accent-foreground: oklch(0.35 0.15 250);
  --ring: oklch(0.55 0.18 250);
}

.dark {
  --primary: oklch(0.7 0.18 250);
  --primary-foreground: oklch(0.15 0.02 250);
}

All 25 screens automatically inherit the new colors -- buttons, badges, links, charts, sidebar accents, and focus rings all update at once.

Changing Typography

Configure fonts in app/layout.tsx:

import { Inter, DM_Sans, JetBrains_Mono } from "next/font/google";

const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-dm-sans" });
const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains" });

Swap these for any Google Font or local font files. The CSS variables --font-sans, --font-heading, and --font-mono control body text, headings, and code respectively.

Replacing Seed Data

All mock data lives in data/seed.ts. Replace it with your data source:

Static data

import { getPosts } from "@/lib/api"

export default async function BlogHome() {
  const posts = await getPosts()
  return <PostGrid posts={posts} />
}

CMS integration (Sanity, Contentful)

import { sanityClient } from "@/lib/sanity"

const posts = await sanityClient.fetch(`*[_type == "post"] | order(publishedAt desc)`)

Database (Prisma, Drizzle)

import { db } from "@/lib/db"

const posts = await db.post.findMany({
  orderBy: { publishedAt: "desc" },
  include: { author: true },
})

Extending Components

Use the cn() utility (from lib/utils.ts) to add custom classes without conflicts:

import { Button } from "@/components/ui/button"

<Button className="rounded-full shadow-lg" size="lg">
  Subscribe
</Button>

Adding Component Variants

Components use class-variance-authority for variant management:

const buttonVariants = cva("...", {
  variants: {
    variant: {
      default: "...",
      brand: "bg-brand-500 text-white hover:bg-brand-600",
    },
  },
})

Customizing the Blog Header

Edit components/layout/blog-header.tsx to:

  • Add or remove navigation links -- update the navLinks array
  • Change the logo -- replace the "B" icon and "BlogCMS" text with your brand
  • Add/remove header actions -- the search, RSS, and theme toggle buttons are individually removable
const navLinks = [
  { label: "Home", href: "/" },
  { label: "Categories", href: "/categories/technology" },
  { label: "About", href: "/about" },
  { label: "Newsletter", href: "/newsletter" },
  // Add your links here
]

Customizing the Admin Sidebar

Edit components/layout/admin-sidebar.tsx to add or remove navigation items:

const navItems = [
  { label: "Dashboard", href: "/admin", icon: LayoutDashboard },
  { label: "Posts", href: "/admin/posts", icon: FileText },
  { label: "Media", href: "/admin/media", icon: Image },
  // Remove items you don't need
  // Add items for new admin pages
]

Each item takes a label, href, and a Lucide icon component. Active state highlighting is handled automatically based on the current pathname.

Adding New Blog Pages

  1. Create a new route in app/(blog)/your-page/page.tsx
  2. The page automatically inherits the blog layout (header, footer)
  3. Add a navigation link in components/layout/blog-header.tsx
// app/(blog)/resources/page.tsx
export default function ResourcesPage() {
  return (
    <main id="main-content" className="mx-auto max-w-6xl px-4 py-12">
      <h1 className="font-heading text-3xl font-bold">Resources</h1>
      {/* Your content */}
    </main>
  )
}

Adding New Admin Pages

  1. Create a new page in app/(admin)/admin/your-page/page.tsx
  2. Add a sidebar link in components/layout/admin-sidebar.tsx
  3. Create any needed composites in components/admin/
// app/(admin)/admin/seo/page.tsx
export default function SEOPage() {
  return (
    <div className="space-y-6">
      <h1 className="font-heading text-2xl font-bold">SEO Settings</h1>
      {/* Your content */}
    </div>
  )
}

Integrating a Rich Text Editor

The kit includes a post editor page at app/(admin)/admin/posts/new/page.tsx with a placeholder textarea. To add a full rich text editor:

pnpm add novel
import { Editor } from "novel"

<Editor
  defaultValue={post.content}
  onUpdate={(editor) => setContent(editor.getHTML())}
/>

Tiptap

pnpm add @tiptap/react @tiptap/starter-kit @tiptap/extension-image
import { useEditor, EditorContent } from "@tiptap/react"
import StarterKit from "@tiptap/starter-kit"

const editor = useEditor({ extensions: [StarterKit] })
<EditorContent editor={editor} />

Integrating a Real Comment System

The kit renders comments from seed data. Replace with a live comment system:

Giscus (GitHub Discussions)

pnpm add @giscus/react
import Giscus from "@giscus/react"

<Giscus
  repo="your-org/your-repo"
  repoId="..."
  category="Blog Comments"
  categoryId="..."
  mapping="pathname"
  theme="preferred_color_scheme"
/>

Disqus

pnpm add disqus-react
import { DiscussionEmbed } from "disqus-react"

<DiscussionEmbed shortname="your-shortname" config={{ url, identifier, title }} />

Integrating Newsletter Providers

The NewsletterForm component in components/blog/newsletter-form.tsx currently handles email collection on the client. Wire it to your email provider:

Resend

// app/api/subscribe/route.ts
import { Resend } from "resend"
const resend = new Resend(process.env.RESEND_API_KEY)

export async function POST(request: Request) {
  const { email } = await request.json()
  await resend.contacts.create({ email, audienceId: "..." })
  return Response.json({ success: true })
}

ConvertKit

await fetch(`https://api.convertkit.com/v3/forms/${formId}/subscribe`, {
  method: "POST",
  body: JSON.stringify({ api_key: process.env.CONVERTKIT_API_KEY, email }),
})

Mailchimp

await fetch(`https://${dc}.api.mailchimp.com/3.0/lists/${listId}/members`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MAILCHIMP_API_KEY}` },
  body: JSON.stringify({ email_address: email, status: "subscribed" }),
})

Removing Unused Features

Delete directories you don't need:

  • Don't need the newsletter? Delete app/(blog)/newsletter/ and components/blog/newsletter-form.tsx
  • Don't need comments? Delete app/(admin)/admin/comments/ and components/blog/comment-card.tsx
  • Don't need analytics? Delete app/(admin)/admin/analytics/ and components/admin/analytics-chart.tsx
  • Don't need scheduled posts? Delete app/(admin)/admin/scheduled/
  • Run pnpm build to verify no broken imports

Using shadcn/ui CLI

Add new components alongside the kit:

npx shadcn@latest add <component-name>

Components install to components/ui/ and integrate seamlessly with the existing design tokens. The kit uses the new-york style with CSS variables enabled.