v0.17.5 BLOG EXAMPLE / COURSES / GUIDES
Education
blog.
Lessons organized into progressive, practical series. Preview the finished page, then inspect the exact source compiled by the current Kudzu release.
02
PROJECT SOURCE
Build it yourself
function Tutorial({ blog }: { blog: BlogExample }) {
return (
<div className="tpl tpl-tutorial">
<header>
<a href="#home">OPEN LESSON</a>
<nav>
<a href="#courses">Courses</a>
<a href="#notes">Notes</a>
<a href="#about">About</a>
</nav>
<a href="/example">Examples ↗</a>
</header>
<main>
<section className="lesson-cover">
<div>
<span>FREE COURSE · BEGINNER · 48 MIN</span>
<h1>
Build a website
<br />
that lasts.
</h1>
<p>
A practical introduction to semantic HTML, resilient CSS, and
necessary behavior.
</p>
<a
className="story-link"
href={`/example/blog/${blog.slug}/demo/${blog.posts[0].slug}`}
>
Start lesson one →
</a>
</div>
<aside>
<strong>3</strong>
<span>lessons</span>
<strong>1</strong>
<span>small project</span>
</aside>
</section>
<section className="lesson-progress">
<div>
<span>YOUR PROGRESS</span>
<strong>0%</strong>
</div>
<i />
</section>
<ol className="lesson-list">
{blog.posts.map((post, index) => (
<li>
<span>0{index + 1}</span>
<div>
<small>
{index ? "15 MIN · READING" : "18 MIN · PRACTICAL"}
</small>
<h2>
<a href={`/example/blog/${blog.slug}/demo/${post.slug}`}>
{post.title}
</a>
</h2>
<p>
{index
? "Build on the previous lesson with one focused technique."
: "Start with the document before thinking about components."}
</p>
</div>
<a
className="story-link"
href={`/example/blog/${blog.slug}/demo/${post.slug}`}
>
{index ? "Preview" : "Begin →"}
</a>
</li>
))}
</ol>
<aside className="lesson-teacher">
<div>SK</div>
<p>
<strong>Taught by Sun Kim</strong>Designer and educator focused on
the durable web.
</p>
</aside>
</main>
</div>
);
}export function BlogArticle({ blog, post }: { blog: BlogExample; post: Post }) {
const index = blog.posts.findIndex((entry) => entry.slug === post.slug);
const previous =
blog.posts[(index + blog.posts.length - 1) % blog.posts.length];
const next = blog.posts[(index + 1) % blog.posts.length];
const feature = articleFeature(blog.layout);
const copy = articleCopy(blog.layout);
const section = articleSection(blog.layout);
const deck = articleDeck(blog.layout);
const author = articleAuthor(blog.layout);
const heading = articleHeading(blog.layout);
const quote = articleQuote(blog.layout);
return (
<div className={`article-template article-${blog.layout}`}>
<header>
<a href={`/example/blog/${blog.slug}/demo`}>{blog.publication}</a>
<nav>
<a href={`/example/blog/${blog.slug}/demo`}>All stories</a>
<a href="/example">Kudzu examples ↗</a>
</nav>
</header>
<main>
<div className="article-breadcrumb">
<a href={`/example/blog/${blog.slug}/demo`}>{blog.name}</a>
<span>/</span>
<span>{post.meta}</span>
</div>
<article>
<header>
<span>{section}</span>
<h1>{post.title}</h1>
<p>{deck}</p>
<div>
<span>By {author}</span>
<time>July 25, 2026 · 8 min read</time>
</div>
</header>
<figure
className="article-hero"
role="img"
aria-label={`${post.title} cover`}
/>
<div className="article-content">
{feature}
<div className="article-copy">
<p className="article-opening">{copy[0]}</p>
<h2>{heading}</h2>
<p>{copy[1]}</p>
<blockquote>{quote}</blockquote>
<p>{copy[2]}</p>
</div>
</div>
</article>
<footer className="article-pagination">
<a href={`/example/blog/${blog.slug}/demo/${previous.slug}`}>
<span>← Previous</span>
<strong>{previous.title}</strong>
</a>
<a href={`/example/blog/${blog.slug}/demo/${next.slug}`}>
<span>Next →</span>
<strong>{next.title}</strong>
</a>
</footer>
</main>
</div>
);
}
// Layout-specific feature
case "tutorial":slug: "tutorial",import {
BlogDemo,
BlogExample,
blogExamples,
} from "../../../../components/examples/BlogExamples";
import {
inlineStyles,
loadBlogStyles,
} from "../../../../components/examples/ExampleSource";
export const metadata = {
title: "Kudzu Blog Demo",
description: "A static blog design compiled with Kudzu 0.17.5 TSX.",
lang: "en",
locale: "en_US",
siteName: "Kudzu Examples",
styles: true,
};
export function getStaticPaths() {
return blogExamples.map((blog) => ({
params: { slug: blog.slug },
props: { blog },
}));
}
export default async function BlogDemoPage({ blog }: { blog: BlogExample }) {
const css = await loadBlogStyles(blog);
return [inlineStyles(css), BlogDemo({ blog })];
}
import { BlogArticle } from "../../../../../components/examples/BlogArticles";
import {
BlogExample,
blogExamples,
} from "../../../../../components/examples/BlogExamples";
import {
inlineStyles,
loadBlogStyles,
} from "../../../../../components/examples/ExampleSource";
type Post = BlogExample["posts"][number];
export const metadata = {
title: "Kudzu Blog Article Demo",
description: "A statically generated article page compiled with Kudzu 0.17.5 TSX.",
lang: "en",
locale: "en_US",
siteName: "Kudzu Examples",
styles: true,
};
export function getStaticPaths() {
return blogExamples.flatMap((blog) =>
blog.posts.map((post) => ({
params: { slug: blog.slug, post: post.slug },
props: { blog, post },
})),
);
}
export default async function BlogArticlePage({
blog,
post,
}: {
blog: BlogExample;
post: Post;
}) {
const css = await loadBlogStyles(blog);
return [inlineStyles(css), BlogArticle({ blog, post })];
}
.tpl {
min-height: 100vh;
}
.tpl,
.tpl * {
box-sizing: border-box;
}
.tpl > main,
.tpl main {
width: auto;
margin: 0;
}
.tpl a {
text-decoration: none;
}
.tpl button,
.tpl input {
font: inherit;
}
.tpl nav {
font-family: inherit;
}
.tpl h1 a,
.tpl h2 a,
.tpl h3 a {
color: inherit;
}
.tpl .story-link {
display: inline-block;
padding: 11px 14px;
color: inherit;
border: 1px solid currentColor;
}
.article-template {
min-height: 100vh;
color: #25211d;
background: #f3efe7;
font-family: Georgia, serif;
}
.article-template > header,
.article-template > main {
width: min(1120px, calc(100% - 40px));
margin: auto;
}
.article-template > header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 82px;
border-bottom: 1px solid currentColor;
}
.article-template > header > a {
color: inherit;
font-size: 25px;
font-weight: 700;
}
.article-template > header nav a {
color: inherit;
opacity: 0.7;
}
.article-breadcrumb {
display: flex;
gap: 12px;
padding: 30px 0;
color: #766f67;
font: 10px var(--mono);
text-transform: uppercase;
}
.article-breadcrumb a {
color: inherit;
}
.article-template article > header {
max-width: 920px;
padding: 50px 0 65px;
}
.article-template article > header > p {
max-width: 720px;
font-size: 20px;
line-height: 1.65;
}
.article-template article > header > div {
display: flex;
gap: 30px;
margin-top: 28px;
color: #746c64;
font: 10px var(--mono);
}
.article-hero {
min-height: 620px;
margin: 0;
background: linear-gradient(130deg, #c55e40, #e7b778);
background-position: center;
background-size: cover;
}
.article-content {
display: grid;
grid-template-columns: 240px minmax(0, 680px);
gap: 70px;
justify-content: center;
padding: 80px 0 120px;
}
.article-content > aside {
align-self: start;
display: flex;
flex-direction: column;
gap: 13px;
padding: 22px;
border-top: 3px solid currentColor;
background: #e7dfd2;
font: 11px var(--mono);
line-height: 1.6;
}
.article-content > aside a {
color: inherit;
border-bottom: 1px solid;
}
.article-copy {
font-size: 18px;
line-height: 1.85;
}
.article-copy .article-opening:first-letter {
float: left;
margin: 8px 10px 0 0;
font-size: 76px;
line-height: 0.7;
}
.article-copy h2 {
margin: 60px 0 20px;
font-size: 38px;
line-height: 1.1;
}
.article-copy blockquote {
margin: 55px 0;
padding-left: 28px;
border-left: 3px solid #b64b31;
font: italic 31px/1.45 Georgia;
}
.article-pagination {
display: grid;
grid-template-columns: 1fr 1fr;
width: auto;
padding: 0 0 80px;
border-top: 1px solid currentColor;
}
.article-pagination a {
display: flex;
flex-direction: column;
gap: 12px;
padding: 30px;
color: inherit;
}
.article-pagination a:last-child {
border-left: 1px solid;
text-align: right;
}
.article-pagination span {
color: #81786f;
font: 9px var(--mono);
}
.article-pagination strong {
font-size: 20px;
}
.article-ingredients ul,
.article-takeaways ol {
margin: 0;
padding-left: 20px;
}
.article-developer,
.article-changelog,
.article-photography,
.article-community {
color: #e6edf3;
background: #0d1117;
font-family: var(--mono);
}
.article-developer article > header h1,
.article-changelog article > header h1 {
font-family: var(--mono);
}
.article-developer article > header > span,
.article-changelog article > header > span {
color: #56d29a;
}
.article-developer .article-content > aside,
.article-changelog .article-content > aside,
.article-community .article-content > aside {
background: #161b22;
}
.article-developer .article-copy blockquote,
.article-changelog .article-copy blockquote {
border-color: #56d29a;
}
.article-template article > header > span {
color: #b64b31;
font: 10px var(--mono);
letter-spacing: 0.13em;
}
.article-template article > header h1 {
margin: 22px 0;
font:
600 clamp(60px, 9vw, 120px)/0.87 Georgia,
serif;
letter-spacing: -0.07em;
}
.article-content > aside > strong:first-child,
.article-content > aside > span:first-child {
color: #b64b31;
font-size: 10px;
letter-spacing: 0.1em;
}
.article-editorial .article-hero {
background-image: url("https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?auto=format&fit=crop&w=1500&q=85");
}
@media (max-width: 800px) {
.article-template > header,
.article-template > main {
width: min(100% - 28px, 1120px);
}
.article-content {
grid-template-columns: 1fr;
gap: 40px;
padding-top: 50px;
}
.article-content > aside {
position: static;
}
.article-hero {
min-height: 440px;
}
.tpl > header nav {
display: none;
}
}
@media (max-width: 480px) {
.article-template > header nav a:first-child {
display: none;
}
.article-template article > header > div {
align-items: flex-start;
flex-direction: column;
gap: 8px;
}
.article-pagination {
grid-template-columns: 1fr;
}
.article-pagination a:last-child {
border-top: 1px solid;
border-left: 0;
text-align: left;
}
}
.tpl-tutorial {
color: #201a0d;
background: #fff9e7;
font-family: Arial;
}
.tpl-tutorial > header,
.tpl-tutorial > main {
width: min(1120px, calc(100% - 40px));
margin: auto;
}
.tpl-tutorial > header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 78px;
border-bottom: 1px solid #c9b986;
}
.tpl-tutorial > header > a:first-child {
color: inherit;
font-weight: 900;
}
.tpl-tutorial > header > a:last-child {
color: inherit;
font-size: 11px;
}
.tpl-tutorial nav a {
color: inherit;
}
.article-tutorial {
color: #201a0d;
background: #fff9e7;
font-family: Arial;
}
.article-tutorial .article-hero {
background: linear-gradient(120deg, #ffbd38 65%, #201a0d 65%);
}
.article-tutorial .article-content > aside {
background: #eee4c6;
}
.lesson-cover {
display: grid;
grid-template-columns: 1fr 220px;
gap: 50px;
padding: 80px;
margin-top: 50px;
background: #ffbd38;
}
.lesson-cover span {
font: 10px var(--mono);
}
.lesson-cover h1 {
margin: 20px 0;
font-size: clamp(60px, 8vw, 100px);
line-height: 0.87;
letter-spacing: -0.075em;
}
.lesson-cover p {
max-width: 650px;
font-size: 18px;
}
.lesson-cover button {
padding: 14px;
border: 1px solid;
background: transparent;
}
.lesson-cover aside {
display: grid;
grid-template-columns: 1fr 1fr;
align-content: end;
border-left: 1px solid;
padding-left: 30px;
}
.lesson-cover aside strong {
font-size: 50px;
}
.lesson-progress {
padding: 35px 0;
}
.lesson-progress > div {
display: flex;
justify-content: space-between;
font: 10px var(--mono);
}
.lesson-progress i {
display: block;
height: 6px;
margin-top: 12px;
background: linear-gradient(90deg, #ffbd38 8%, #e3d9b9 8%);
}
.lesson-list {
padding: 0;
list-style: none;
}
.lesson-list li {
display: grid;
grid-template-columns: 70px 1fr auto;
gap: 25px;
align-items: center;
padding: 32px 0;
border-top: 1px solid #c9b986;
}
.lesson-list li > span {
font: 28px var(--mono);
}
.lesson-list small {
color: #866d2f;
font: 9px var(--mono);
}
.lesson-list h2 {
margin: 8px 0;
font-size: 29px;
}
.lesson-list p {
color: #756a4e;
}
.lesson-list button {
padding: 11px;
border: 1px solid;
background: transparent;
}
.lesson-teacher {
display: flex;
gap: 20px;
align-items: center;
margin: 50px 0 80px;
padding: 25px;
background: #eee4c6;
}
.lesson-teacher > div {
display: grid;
place-items: center;
width: 60px;
height: 60px;
background: #201a0d;
border-radius: 50%;
color: white;
}
.lesson-teacher p {
display: flex;
flex-direction: column;
}
.article-progress i {
height: 6px;
background: linear-gradient(90deg, #ffbd38 33%, #d4caab 33%);
}
@media (max-width: 800px) {
.lesson-cover {
grid-template-columns: 1fr;
padding: 40px;
}
.lesson-cover aside {
border-top: 1px solid;
border-left: 0;
padding: 25px 0 0;
}
.lesson-list li {
grid-template-columns: 50px 1fr;
}
.lesson-list li button {
grid-column: 2;
justify-self: start;
}
}