Production-ready Next.js 16 blog powered by Morph SDK
A real, deployable blog example — auto dark mode, AI-generated themes, behavioral intelligence, and reading-mode adaptation, on long-form MDX content.
This isn't a stripped-down demo. It's a fully functional blog you can fork and ship today:
- ✅ Real article content — 3 long-form articles in MDX
- ✅ Auto dark mode that matches your brand colors (Claude-generated, WCAG AA validated)
- ✅ AI-generated TL;DR card on every article
- ✅ Auto-generated table of contents (sticky, scroll-spy)
- ✅ Reading progress bar with adaptive color
- ✅
MorphZonetracking on every article section - ✅
/playgroundroute with isolated demos for every Morph SDK feature - ✅ Tailwind v4 + CSS-variable theming, no shadcn dependency
- ✅ 100% static generation — Lighthouse 95+ out of the box
git clone https://github.com/morphuiapp/morphui-nextjs-blog
cd morphui-nextjs-blog
npm install
cp .env.example .env.local
# .env.local already points at the public demo key (cha-free-demo) — works immediately.
# Replace it with your own from https://app.morphui.dev/dashboard
npm run devOpen http://localhost:3000.
Vercel will prompt you to set NEXT_PUBLIC_MORPH_KEY — use cha-free-demo for
preview deploys, or grab a free key from
app.morphui.dev/dashboard.
"use client";
import { MorphProvider } from "@morphuiapp/morphui";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<MorphProvider
licenseKey={process.env.NEXT_PUBLIC_MORPH_KEY ?? "cha-free-demo"}
collapse
>
{children}
</MorphProvider>
);
}collapse opts in to the live behavioral collapse engine. With it on, sections
flagged data-morph-collapsable fold up automatically after enough low-engagement
signals — and only then.
import { MorphZone } from "@morphuiapp/morphui";
<MorphZone id="article-content" priority={2}>
<div className="prose" dangerouslySetInnerHTML={{ __html: article.html }} />
</MorphZone>:root {
--background: var(--morph-bg, #fafafc);
--foreground: var(--morph-text-primary, #0a0a0f);
--primary: var(--morph-primary, #4f46e5);
--card: var(--morph-card-bg, #f1f0f7);
--border: var(--morph-border, #e2e8f0);
}Every brand color references a --morph-* CSS variable with a static fallback.
Morph populates the --morph-* side at boot — you keep the fallback as your
light-mode palette.
The blog uses Morph 0.4's explicit opt-in / opt-out contracts to keep critical sections stable while letting non-essential sections fold:
{/* Pinned: never reordered, never collapsed, never faded */}
<section data-morph-pin>
<Hero />
</section>
{/* Opt-in to collapse: engine may fold this after low engagement */}
<section data-morph-collapsable>
<RelatedArticles />
</section>What's pinned in this blog:
- The hero (
<section data-morph-pin>on the home page) - The articles grid (it's the core content)
- The bottom CTA card
- The site footer (auto-pinned by
<footer>tag, but explicit pin makes the intent visible) - Form / phone-input demos (auto-protected anyway, but explicit)
What's opt-in collapsable on /playground:
- ImproperTap, LostTap, MorphLoader, MorphDialog demos
- Pin-status and behavior-debug panels
Auto-detect by default. Without any attribute, Morph auto-pins semantic landmarks (
<nav>,<header>,<footer>,<aside>), elements with ARIA landmark roles, and elements whose class/id containshero,sticky, orpinned. Usedata-morph-pin-offto opt back into tracking inside an auto-pinned region.
Full docs on behavioral exclusions →
Walk through this scenario:
- First visit — light mode, your palette
- Open at night / system dark — auto dark variant generated by Claude (Pro+) or fallback dark (Free), atomically applied with no flash
- Navigate between articles —
PreNavSkeletonpaints a same-shape placeholder in the gap between unmount and mount, so the page never goes white - Refresh mid-article —
ScrollRestorationsnaps you back to where you were - Read 1–2 articles slowly — Morph starts tracking section-level engagement
via
MorphZone. After enough sessions, hot zones float to the top - Visit
/playground— every SDK feature isolated in its own card so you can poke at it
| Signal | Morph response |
|---|---|
| First visit (system dark) | Claude-generated dark theme (atomic apply, WCAG-validated) |
| Reads slowly | Article mode (denser typography) |
| Skims fast | Digest mode (key points first) |
prefers-contrast: more |
High-contrast palette + WCAG bump |
prefers-reduced-motion |
Transitions disabled |
| Returns several times | Hot zones rise via MorphZone |
Low engagement on a data-morph-collapsable section |
Section folds up |
These run inside MorphProvider with no extra code. Each is a lazy chunk —
zero overhead if disabled.
| Engine | What it does on this blog |
|---|---|
PreNavSkeleton |
Paints same-shape placeholder during route transitions — no white flash between articles |
WhiteIslandFixer |
Dims the YouTube embed and white-bg logos (visible on /playground) in dark mode |
ScrollRestoration |
Restores scroll position after refresh, even mid-article |
ImproperTapDetector |
Mobile near-miss recovery (active on /playground demo card) |
LostTapDetector |
Logs taps that never land on a button (visible on /playground) |
Disable any with a prop: <MorphProvider preNav={false}>, etc.
Visit /playground (linked from the footer, marked noindex) to see every
Morph feature isolated in its own card with inline instructions:
- ImproperTap — mobile-only, requires DevTools
⌘⇧M - LostTap — requires DevTools console open
- MorphForm — fill, navigate away, come back; values restored
- MorphLoader — adaptive 4-stage loader (none → light → active → slow)
- MorphDialog — accessible focus-trap modal
- MorphPhoneInput — paste any format, get E.164 out
- Pin status — live view of every V2-tracked zone with its pinned reason
- Behavior debug — live ScorerEngine inputs (clicks per zone)
- WhiteIsland — toggle macOS to dark and watch the iframe + white logo dim
- Utility hooks —
useStorage,useTabSync,useAutoFillGuardcombined
The playground is <meta robots="noindex,nofollow"> so it doesn't pollute SEO.
Edit the brand block at the top of app/globals.css:
:root {
--brand-bg: #ffffff;
--brand-surface: #f8fafc;
--brand-primary: #4f46e5;
--brand-text: #0f172a;
}Morph reads these on boot and asks Claude (with temperature: 0 for
determinism) to generate a coherent dark mode that preserves your hue ±15°
and passes WCAG AA on every text element. The result is cached in Supabase
- localStorage — same brand → same theme, every time.
morphui-nextjs-blog/
├── app/
│ ├── layout.tsx ← Root + Providers wrap
│ ├── page.tsx ← Home: hero + articles grid + CTA
│ ├── playground/page.tsx ← Feature explorer (noindex)
│ ├── articles/[slug]/page.tsx ← Static-generated article page
│ └── globals.css ← Brand palette + Morph CSS vars + prose
├── components/
│ ├── providers.tsx ← MorphProvider wrapper
│ ├── article-card.tsx ← Home grid card
│ ├── article-layout.tsx ← Article shell with MorphZones
│ ├── article-toc.tsx ← Scroll-spy TOC
│ ├── reading-progress.tsx ← Top-of-page progress bar
│ ├── tldr-summary.tsx ← AI TL;DR card
│ ├── theme-indicator.tsx ← useMorph() badge in hero
│ ├── site-header.tsx
│ ├── site-footer.tsx ← Includes /playground link
│ ├── morph-form-demo.tsx ← /playground demos
│ ├── morph-loader-demo.tsx
│ ├── morph-dialog-demo.tsx
│ ├── morph-phone-demo.tsx
│ ├── improper-tap-demo.tsx
│ ├── lost-tap-demo.tsx
│ ├── pin-status-demo.tsx
│ ├── behavior-debug.tsx
│ └── utility-hooks-demo.tsx
├── content/
│ ├── articles/*.mdx ← The 3 sample articles
│ └── lib/articles.ts ← Frontmatter + remark pipeline
└── lib/utils.ts ← cn() helper
- Framework: Next.js 16 (App Router, Turbopack)
- Language: TypeScript
- UI: React 19 + Tailwind CSS v4 (CSS-first, no config file)
- Content: MDX with
remark/rehypepipeline - SDK:
@morphuiapp/morphuiv0.4 - Deployment: Vercel-ready, but works anywhere static
- Lighthouse Performance: 96
- Lighthouse Accessibility: 100
- Lighthouse Best Practices: 100
- Lighthouse SEO: 100
- LCP: 1.3s
- CLS: 0.05
Static generation means every article is pre-rendered at build time — Morph runs entirely client-side after first paint without blocking it.
The repo ships with 3 sample articles demonstrating different content types:
- Building Intelligent UIs in 2026 — Long-form essay
- 7 Dark Mode Mistakes Every Dev Makes — Listicle with code blocks
- Building Grip Detection in Flutter — Technical deep-dive
Replace them with your own MDX in content/articles/ — the home page picks them
up automatically via the getAllArticles() reader.
This is a learning resource — clone it, modify it, share what you build!
If you find a bug or have a suggestion:
MIT — clone, fork, customize, ship.
This example is meant to be copied. Use it as a starting point for your own intelligent blog or content site.
