Self-contained specification to regenerate the full Payload CMS website template from scratch using code. No Figma file needed.
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, React 19, TypeScript) |
| CMS | Payload CMS 3 (local API, SQLite by default — swap adapter for MongoDB or PostgreSQL in production) |
| Styling | Tailwind CSS v4 + shadcn/ui |
| Font | Geist Sans + Geist Mono (via geist npm package) |
| Rich text | Lexical (via @payloadcms/richtext-lexical) |
| Forms | @payloadcms/plugin-form-builder |
| SEO | @payloadcms/plugin-seo |
| Search | @payloadcms/plugin-search |
| Auth | Payload built-in (admin user) |
All @payloadcms/* packages must be pinned to the same version as payload itself (e.g. if payload is 3.33.0, every @payloadcms/* is also 3.33.0). Do not mix patch versions.
Key packages:
payload
@payloadcms/next
@payloadcms/richtext-lexical
@payloadcms/db-sqlite ← default dev adapter
@payloadcms/plugin-seo
@payloadcms/plugin-form-builder
@payloadcms/plugin-search
geist
tailwindcss ← v4
@tailwindcss/postcss ← required for Tailwind v4 (postcss.config.mjs)
@tailwindcss/typography
sharp ← pnpm: add enable-pre-post-scripts=true to .npmrc so sharp builds
Environment variables (.env):
DATABASE_URI=file:./payload.db
PAYLOAD_SECRET=your-secret-change-in-production
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
| CSS variable | oklch | Hex |
|---|---|---|
--background |
oklch(100% 0 0) |
#ffffff |
--foreground |
oklch(14.5% 0 0) |
#1a1a1a |
--card |
oklch(96.5% 0.005 265) |
#f5f5f8 |
--card-foreground |
oklch(14.5% 0 0) |
#1a1a1a |
--primary |
oklch(20.5% 0 0) |
#252525 |
--primary-foreground |
oklch(98.5% 0 0) |
#fbfbfb |
--secondary |
oklch(97% 0 0) |
#f7f7f7 |
--secondary-foreground |
oklch(20.5% 0 0) |
#252525 |
--muted |
oklch(97% 0 0) |
#f7f7f7 |
--muted-foreground |
oklch(55.6% 0 0) |
#767676 |
--border |
oklch(92.2% 0 0) |
#ebebeb |
--input |
oklch(92.2% 0 0) |
#ebebeb |
--ring |
oklch(70.8% 0 0) |
#b3b3b3 |
| CSS variable | oklch | Hex |
|---|---|---|
--background |
oklch(14.5% 0 0) |
#0f0f0f |
--foreground |
oklch(98.5% 0 0) |
#f5f5f5 |
--card |
oklch(17% 0 0) |
#1c1c1c |
--card-foreground |
oklch(98.5% 0 0) |
#f5f5f5 |
--primary |
oklch(98.5% 0 0) |
#f5f5f5 |
--primary-foreground |
oklch(20.5% 0 0) |
#252525 |
--secondary |
oklch(26.9% 0 0) |
#2e2e2e |
--secondary-foreground |
oklch(98.5% 0 0) |
#f5f5f5 |
--muted |
oklch(26.9% 0 0) |
#2e2e2e |
--muted-foreground |
oklch(70.8% 0 0) |
#a3a3a3 |
--border |
oklch(26.9% 0 0) |
#2e2e2e |
--input |
oklch(26.9% 0 0) |
#2e2e2e |
--ring |
oklch(43.9% 0 0) |
#5c5c5c |
| Variable | Value |
|---|---|
--radius (base) |
0.625rem (10px) |
--radius-sm |
calc(var(--radius) - 4px) = 6px |
--radius-md |
calc(var(--radius) - 2px) = 8px |
--radius-lg |
var(--radius) = 10px |
--radius-xl |
calc(var(--radius) + 4px) = 14px |
| Name | Value |
|---|---|
| sm | 40rem (640px) |
| md | 48rem (768px) |
| lg | 64rem (1024px) |
| xl | 80rem (1280px) |
| 2xl | 86rem (1376px) |
.container {
width: 100%;
margin-inline: auto;
padding-inline: 1rem; /* mobile */
}
/* md: padding-inline: 2rem; max-width: 768px */
/* lg: max-width: 1024px */
/* xl: max-width: 1280px */
/* 2xl: max-width: 1376px */Font: Geist Sans (variable, loaded via geist/font/sans). Mono: Geist Mono.
Applied via CSS variables --font-sans and --font-mono on <html>.
| Style | Size | Line Height | Weight | Class |
|---|---|---|---|---|
| H1 | 48px (3rem) | 52px | 700 (bold) | prose h1 |
| H2 | 36px (2.25rem) | 40px | 700 | prose h2 |
| H3 | 24px (1.5rem) | 28px | 600 (semibold) | prose h3 |
| Body | 16px (1rem) | 24px (1.5rem) | 400 | base |
| Small | 14px (0.875rem) | 20px | 400 | text-sm |
| XS | 12px (0.75rem) | 16px | 400 | text-xs |
| Label / Button | 14px | 20px | 500 (medium) | text-sm font-medium |
Typography is applied through @tailwindcss/typography prose classes (prose dark:prose-invert).
Global heading overrides reset default browser weights: h1–h6 { font-weight: unset; font-size: unset; } — sizes come from prose plugin.
Dark mode uses a data-theme attribute on <html>, not prefers-color-scheme.
@custom-variant dark (&:is([data-theme='dark'] *));Implementation:
InitThemescript injected in<head>readslocalStorageand setsdata-themebefore first paint to prevent flash.ThemeSelectorcomponent (in footer) writes tolocalStorageand updateshtml[data-theme].- The header adapts per-page: on pages with
HighImpactHero, the hero callssetHeaderTheme('dark')so the transparent header shows white text. On normal pages, the header is light with dark text. - HTML starts with
opacity: 0and becomes visible oncedata-themeis set:
html {
opacity: 0;
}
html[data-theme='dark'],
html[data-theme='light'] {
opacity: initial;
}Variants: default | outline | ghost | link | secondary | destructive
Sizes: default (h-10) | sm (h-9) | lg (h-11) | icon (size-10)
// Default (filled primary)
<Button variant="default">All posts</Button>
// bg-primary text-primary-foreground, px-4 py-2, h-10, rounded-md
// Outline
<Button variant="outline">Contact</Button>
// border border-input bg-background, px-4 py-2, h-10, rounded-md<article className='border border-border rounded-lg overflow-hidden bg-card hover:cursor-pointer'>
{/* Image fills top */}
<div className='relative w-full'>
<Media resource={metaImage} size='33vw' />
</div>
<div className='p-4'>
{/* Category — uppercase text-sm mb-4 */}
{/* H3 title — prose h3 */}
{/* Description — mt-2 paragraph */}
</div>
</article><header className='container relative z-20' data-theme={theme}>
<div className='py-8 flex justify-between'>
<Logo /> {/* Payload logo, inverted in light mode: className="invert dark:invert-0" */}
<HeaderNav /> {/* nav links from CMS global */}
</div>
</header>- Position: sticky (sits above page content). On
HighImpactHeropages, the hero uses-mt-[10.4rem]to slide under the header. - Height: ~80px (padding-top/bottom 2rem = 32px × 2 = 64px + logo 34px = ~80px total).
- Theme: transparent with dark/light text depending on
headerThemecontext.
<footer className='mt-auto border-t border-border bg-black dark:bg-card text-white'>
<div className='container py-8 gap-8 flex flex-col md:flex-row md:justify-between'>
<Logo />
<div className='flex flex-col-reverse items-start md:flex-row gap-4 md:items-center'>
<ThemeSelector /> {/* dropdown: Light / Dark / System */}
<nav className='flex flex-col md:flex-row gap-4'>{/* white CMSLink items */}</nav>
</div>
</div>
</footer>Footer nav items (seed): Admin (/admin), Source Code (github.com external), Payload (payloadcms.com external).
Each block has two parts: a Payload config (data model, registered in the Pages collection's layout blocks array) and a React component (renders the data). Both are required.
Usage: Homepage only (hero field, not a layout block).
Payload config: part of the hero group field on Pages, not a block.
React component:
<div className='relative -mt-[10.4rem] flex items-center justify-center text-white' data-theme='dark'>
<div className='container mb-8 z-10 relative flex items-center justify-center'>
<div className='max-w-[36.5rem] md:text-center'>
<RichText /> {/* H1 + body paragraph */}
<ul className='flex md:justify-center gap-4'>{/* Button links */}</ul>
</div>
</div>
<div className='min-h-[80vh]'>
<Media fill /> {/* full-bleed background image */}
</div>
</div>Visual: Full viewport dark section, full-bleed background image, centered text (max 584px), two buttons (default + outline). The -mt-[10.4rem] pulls it behind the sticky header.
Payload config:
{
slug: 'content',
fields: [
{
name: 'columns',
type: 'array',
fields: [
{ name: 'size', type: 'select', options: ['oneThird','half','twoThirds','full'], defaultValue: 'oneThird' },
{ name: 'richText', type: 'richText' },
],
},
],
}React component:
<div className='container my-16'>
<div className='grid grid-cols-4 lg:grid-cols-12 gap-y-8 gap-x-16'>{/* Each column: col-span-4 lg:col-span-{4|6|8|12} */}</div>
</div>Column sizes: full = 12 cols, half = 6 cols, oneThird = 4 cols, twoThirds = 8 cols.
Homepage seed: 1 full-width H2 + 5 one-third columns (feature cards with H3 + paragraph each):
- Admin Dashboard — "Manage this site's pages and posts from the admin dashboard."
- Preview — "Using versions, drafts, and preview, editors can review and share their changes before publishing them."
- Page Builder — "Custom page builder allows you to create unique page, post, and project layouts for any type of content."
- SEO — "Editors have complete control over SEO data and site content directly from the admin dashboard."
- Dark Mode — "Users will experience this site in their preferred color scheme and each block can be inverted."
Payload config:
{
slug: 'mediaBlock',
fields: [
{ name: 'media', type: 'upload', relationTo: 'media', required: true },
{ name: 'position', type: 'select', options: ['default','fullscreen'], defaultValue: 'default' },
],
}React component:
<div className='my-16'>
<Media resource={media} /> {/* full-bleed image */}
</div>Full container-width image, no padding. Used as a visual separator on the homepage.
Payload config:
{
slug: 'archive',
fields: [
{ name: 'introContent', type: 'richText' },
{ name: 'limit', type: 'number', defaultValue: 3 },
{ name: 'categories', type: 'relationship', relationTo: 'categories', hasMany: true },
],
}React component:
<div className='my-16'>
<div className='container mb-16'>
<RichText /> {/* intro: H3 + paragraph */}
</div>
<CollectionArchive posts={posts} /> {/* 3-col grid on desktop */}
</div>CollectionArchive renders a responsive grid of Card components:
<div className='container'>
<div className='grid grid-cols-4 sm:grid-cols-8 lg:grid-cols-12 gap-y-4 gap-x-4 lg:gap-y-8 lg:gap-x-8'>
{posts.map((post) => (
<div className='col-span-4 lg:col-span-4'>
<Card doc={post} relationTo='posts' showCategories />
</div>
))}
</div>
</div>Homepage seed intro: H3 "Recent posts" + body paragraph explaining the archive block.
Payload config:
{
slug: 'cta',
fields: [
{ name: 'richText', type: 'richText' },
{
name: 'links', type: 'array',
fields: [{ name: 'link', type: 'group', fields: [type, reference, url, newTab, label, appearance] }],
},
],
}React component:
<div className='container'>
<div className='bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center'>
<div className='max-w-[48rem] flex items-center'>
<RichText /> {/* H3 + paragraph */}
</div>
<div className='flex flex-col gap-8'>
<CMSLink size='lg' /> {/* button */}
</div>
</div>
</div>Homepage seed: H3 "This is a call to action" + body + "All posts" button (links to /posts).
Payload config:
{
slug: 'formBlock',
fields: [
{ name: 'form', type: 'relationship', relationTo: 'forms', required: true },
{ name: 'introContent', type: 'richText' },
],
}React component:
<div className='container lg:max-w-[48rem]'>
<RichText /> {/* intro: H3 "Example contact form:" */}
<div className='p-4 lg:p-6 border border-border rounded-[0.8rem]'>
<form>
{/* Fields rendered by type: text, email, number, textarea */}
<Button type='submit'>{submitButtonLabel}</Button>
</form>
</div>
</div>Contact form fields (from seed):
- Full Name — text, required, width 100
- Email — email, required, width 100
- Phone — number, optional, width 100
- Message — textarea, required, width 100
- Submit label: "Submit"
Header (transparent over hero, dark theme)
HighImpact Hero
- H1: "Payload Website Template"
- Body: links to /admin and GitHub repo
- Buttons: "All posts" (default) + "Contact" (outline)
- Background: image-hero1.webp (see Seed Images)
Content Block
- H2 "Core features" (full width)
- 5 × oneThird feature cards (see seeds above)
Media Block
- image-post2.webp (full-bleed)
Archive Block
- H3 "Recent posts"
- 3 post cards, auto-populated from 'posts' collection
CTA Block
- H3 "This is a call to action"
- "configured in the admin dashboard" body
- "All posts" button → /posts
Footer
Header (light, sticky, border-bottom)
<div className="container py-24">
<h1>Posts</h1>
<PageRange /> {/* "Showing 1-10 of 12 results" */}
<CollectionArchive posts={posts} /> {/* 3-col card grid, pagination if needed */}
</div>
Footer
PostHero (full-viewport, dark, negative margin like HighImpact)
- bg image (post meta image)
- gradient overlay from bottom
- Breadcrumb category (uppercase, text-sm, muted)
- H1 title (text-3xl md:text-5xl lg:text-6xl, white)
- Author name + published date (flex row, text-sm, muted-white)
Header (overlaid, white text via headerTheme='dark')
Post content (container max-w prose)
- RichText (H2s, paragraphs, inline images, banner blocks)
Related Posts
- H3 "More Posts" or similar
- 2-3 cards
Footer
Header (light, sticky, border-bottom)
<div className="pt-24 pb-24">
<div className="container mb-16">
<div className="prose text-center">
<h1 className="mb-8 lg:mb-16">Search</h1>
<div className="max-w-[50rem] mx-auto">
<Search /> {/* input with ?q= param */}
</div>
</div>
</div>
{results > 0
? <CollectionArchive posts={results} />
: <div className="container">No results found.</div>
}
</div>
Footer
Header (light, sticky, border-bottom — no hero so no overlap)
FormBlock (lg:max-w-[48rem], centered)
- H3 "Example contact form:"
- Bordered card (border border-border rounded-[0.8rem] p-4 lg:p-6):
- Full Name input
- Email input
- Phone input (optional)
- Message textarea
- Submit button
Footer
Fetch these from GitHub raw URLs and upload to the media collection before creating pages/posts that reference them:
| Variable | URL | Used for |
|---|---|---|
imageHero |
https://raw.githubusercontent.com/payloadcms/payload/refs/heads/3.x/templates/website/src/endpoints/seed/image-hero1.webp |
Homepage hero background |
image1 |
https://raw.githubusercontent.com/payloadcms/payload/refs/heads/3.x/templates/website/src/endpoints/seed/image-post1.webp |
Post 1 meta image, Post 3 block image |
image2 |
https://raw.githubusercontent.com/payloadcms/payload/refs/heads/3.x/templates/website/src/endpoints/seed/image-post2.webp |
Post 2 meta image, Homepage media block |
image3 |
https://raw.githubusercontent.com/payloadcms/payload/refs/heads/3.x/templates/website/src/endpoints/seed/image-post3.webp |
Post 3 meta image, Post 2 block image |
Upload pattern:
const imageBuffer = await fetch(url).then((res) => res.arrayBuffer())
const imageDoc = await payload.create({
collection: 'media',
data: { alt: 'Description' },
file: {
data: Buffer.from(imageBuffer),
mimetype: 'image/webp',
name: 'image-hero1.webp',
size: imageBuffer.byteLength,
},
})Post 1 — "Digital Horizons: A Glimpse into Tomorrow"
- Slug:
digital-horizons - Category: Technology
- Meta image:
image1 - Meta description: Explore the cutting edge of technology in our latest roundup.
- Content: H2 lead + paragraphs + banner image block + closing paragraphs
- Author: John Doe
- Published: recent date
Post 2 — "The Future is Now: A Look at Emerging Technologies"
- Slug:
the-future-is-now - Category: Technology
- Meta image:
image2 - Meta description: From AI to quantum computing, the future is arriving faster than ever.
Post 3 — "Navigating the Digital Landscape: A Comprehensive Guide"
- Slug:
navigating-the-digital-landscape - Category: Technology
- Meta image:
image3 - Meta description: Your roadmap to thriving in an increasingly connected world.
Set relatedPosts after all posts are created, using payload.update(). This avoids the validation error that occurs when filterOptions tries to exclude by ID during initial creation:
// Create all 3 posts first, collect their IDs, then:
await payload.update({ collection: 'posts', id: post1Id, data: { relatedPosts: [post2Id, post3Id] } })
await payload.update({ collection: 'posts', id: post2Id, data: { relatedPosts: [post1Id, post3Id] } })
await payload.update({ collection: 'posts', id: post3Id, data: { relatedPosts: [post1Id, post2Id] } })- Title: "Technology"
- Name: John Doe (post author)
When seeding richText fields, the value must be a serialized Lexical editor state. Minimum structure:
{
root: {
type: 'root',
version: 1,
direction: 'ltr',
format: '',
indent: 0,
children: [
// heading node
{
type: 'heading', tag: 'h2',
version: 1, direction: 'ltr', format: '', indent: 0,
children: [{ type: 'text', text: 'Hello world', version: 1 }],
},
// paragraph node
{
type: 'paragraph',
version: 1, direction: 'ltr', format: '', indent: 0,
children: [{ type: 'text', text: 'Body copy here.', version: 1 }],
},
],
},
}Define small helpers at the top of the seed file to keep it readable:
const rt = (...children: object[]) => ({
root: { type: 'root', version: 1, direction: 'ltr', format: '', indent: 0, children },
})
const h = (tag: string, text: string) => ({
type: 'heading',
tag,
version: 1,
direction: 'ltr',
format: '',
indent: 0,
children: [{ type: 'text', text, version: 1 }],
})
const p = (text: string) => ({
type: 'paragraph',
version: 1,
direction: 'ltr',
format: '',
indent: 0,
children: [{ type: 'text', text, version: 1 }],
})| Collection | Key features |
|---|---|
pages |
Flexible layout builder (hero field + blocks array), SEO meta, draft/preview |
posts |
Title, content (Lexical), meta image, categories (relation), author (relation to users), published date |
media |
Image uploads with alt text, focal point, sizes |
categories |
Simple title + slug |
users |
Auth-enabled, name field |
forms |
Form builder plugin — fields, emails, confirmation |
form-submissions |
Stores form submissions |
search |
Search plugin index (synced from posts) |
| Global | Purpose |
|---|---|
header |
Nav items (links array) |
footer |
Nav items (links array) |
| Route | File | Notes |
|---|---|---|
/ |
app/(frontend)/page.tsx |
Fetches pages slug=home |
/posts |
app/(frontend)/posts/page.tsx |
Lists all posts |
/posts/[slug] |
app/(frontend)/posts/[slug]/page.tsx |
Post detail |
/search |
app/(frontend)/search/page.tsx |
Search with ?q= param |
/contact |
app/(frontend)/[slug]/page.tsx |
Generic page with contact slug |
/admin |
Payload admin (built-in) | |
/api |
Payload REST API |
The HighImpactHero and PostHero use -mt-[10.4rem] to slide behind the sticky header (which is ~80px + page top padding). The hero has data-theme="dark" so the header switches to white text via the HeaderTheme context.
Pages and posts support draft mode. A draftMode() Next.js flag shows the AdminBar component at the top when previewing unpublished content.
All images go through a <Media> component that wraps Next.js <Image> with Payload's size variants. The fill prop is used for full-bleed backgrounds; size prop controls responsive srcset hints.
Handles internal/external links, new-tab, and appearance mapping to Button variants:
appearance: 'default'→<Button variant="default">appearance: 'outline'→<Button variant="outline">- No appearance → plain anchor with text-primary + underline
Define without filterOptions on the collection itself (the admin UI handles exclusion). When seeding, always create all posts first and then wire up related posts with payload.update() calls — doing it during initial creation causes a validation error because id is undefined at that point.
src/
app/
layout.tsx ← minimal root layout (no HTML — just returns children)
(frontend)/
globals.css ← all design tokens
layout.tsx ← frontend layout (html, head, body, fonts, providers, header, footer)
page.tsx ← homepage
posts/
page.tsx ← posts listing
[slug]/page.tsx ← post detail
search/page.tsx ← search
[slug]/page.tsx ← generic CMS pages (contact, etc.)
(payload)/
layout.tsx ← minimal layout (just returns children)
admin/
[[...segments]]/
page.tsx ← Payload admin UI
not-found.tsx ← Payload 404
importMap.js ← export const importMap = {}
api/
[...slug]/route.ts ← Payload REST API (GET/POST/PUT/PATCH/DELETE/OPTIONS)
blocks/
ArchiveBlock/
config.ts ← Payload Block definition
Component.tsx ← React component
CallToAction/
config.ts
Component.tsx
Content/
config.ts
Component.tsx
Form/
config.ts
Component.tsx
MediaBlock/
config.ts
Component.tsx
RenderBlocks.tsx ← maps blockType → Component
components/
Card/ ← post card
CollectionArchive/ ← responsive post grid
Logo/ ← Payload logo (SVG)
Media/ ← Next.js image wrapper
RichText/ ← Lexical renderer (uses @payloadcms/richtext-lexical/react)
Link/ ← CMSLink (wraps Button for CMS link data)
Pagination/
PageRange/
ui/
button.tsx ← shadcn button
heros/
HighImpact/ ← full-viewport dark hero
LowImpact/ ← simple page title hero
PostHero/ ← post detail hero with image
RenderHero.tsx
Header/
Component.tsx ← async header (fetches global)
Component.client.tsx ← client header (theme + pathname)
Nav/index.tsx
Footer/
Component.tsx ← async footer
providers/
Theme/
InitTheme.tsx ← inline script to prevent FOUC
ThemeSelector/index.tsx ← Light/Dark/System dropdown
index.tsx
types.ts ← Theme type + themeLocalStorageKey constant
HeaderTheme/index.tsx ← context for per-page header color
index.tsx ← Providers wrapper
collections/
Pages/index.ts
Posts/index.ts
Media.ts
Categories.ts
Users.ts
globals/
Header/index.ts
Footer/index.ts
fields/
defaultLexical.ts
link.ts
linkGroup.ts
endpoints/
seed/index.ts
payload.config.ts
payload-types.ts ← generated by `payload generate:types`
postcss.config.mjs ← required for Tailwind v4: { plugins: { '@tailwindcss/postcss': {} } }
.npmrc ← add enable-pre-post-scripts=true so sharp/esbuild build scripts run
To recreate this template from scratch:
-
Bootstrap:
pnpm create payload-app→ choose "Website" template, or start fromnpx create-next-app+ installpayload. -
Install deps:
payload,@payloadcms/next,@payloadcms/richtext-lexical,@payloadcms/plugin-seo,@payloadcms/plugin-form-builder,@payloadcms/plugin-search,@payloadcms/db-sqlite,geist,tailwindcss@latest,@tailwindcss/postcss,@tailwindcss/typography. All@payloadcms/*packages must be pinned to the same version aspayload. -
Configure pnpm: Add
enable-pre-post-scripts=trueto.npmrcsosharpandesbuildbuild scripts run. -
Apply design tokens: Copy the
:rootand[data-theme='dark']CSS variable blocks from the Design Tokens section above intoglobals.css. -
Set up Geist font: In
(frontend)/layout.tsx, importGeistSansandGeistMonofromgeist/font/*, apply as CSS variables to<html>. -
Build collections: Create
pages,posts,media,categories,userscollections with the fields described above. Register all block configs inPages'slayoutblocks array. -
Build globals: Create
headerandfooterglobals with nav items. -
Build blocks: For each block, create both a
config.ts(Payload Block definition) and aComponent.tsx(React renderer). Register configs in the Pages collection. -
Set up Payload admin routes: Create
app/(payload)/admin/[[...segments]]/page.tsx,not-found.tsx,importMap.js, andapp/(payload)/api/[...slug]/route.ts. These are required for the admin panel and REST API. -
Seed data: Fetch the four seed images from their GitHub raw URLs and upload to media first. Then create posts, category, and pages. Wire up
relatedPostswithpayload.update()after all posts exist. -
Theme system: Implement
InitTheme(inline<script>in<head>that readslocalStorageand setsdata-theme),ThemeSelectordropdown, andHeaderThemecontext. -
Verify pages: Homepage,
/posts,/posts/[slug],/search?q=,/contactshould all render correctly in both light and dark mode.