v0.17.5 BLOG EXAMPLE / AUTHORS / TOPICS

Community
blog.

Multiple authors, shared topics, and popular discussions. Preview the finished page, then inspect the exact source compiled by the current Kudzu release.

01

LIVE OUTPUT

Preview

Open full page ↗
kudzu.local/example/blog/community/demo
02

PROJECT SOURCE

Build it yourself

src/components/examples/BlogTemplates.tsx · CommunityTSX
function Community({ blog }: { blog: BlogExample }) {
  return (
    <div className="tpl tpl-community">
      <header>
        <a href="#home">
          <i />
          COMMON THREAD
        </a>
        <nav>
          <a href="#feed">Feed</a>
          <a href="#topics">Topics</a>
          <a href="#members">Members</a>
        </nav>
        <div>
          <button>Search</button>
          <button>Join community</button>
        </div>
      </header>
      <main>
        <section className="community-hero">
          <div className="community-faces">
            <span>AL</span>
            <span>MK</span>
            <span>JR</span>
            <span>SK</span>
            <span>+42</span>
          </div>
          <h1>
            Good ideas grow
            <br />
            in public.
          </h1>
          <p>
            Questions, projects, and field notes from an independent creative
            community.
          </p>
          <div>
            <strong>2,481</strong> members <strong>184</strong> online today
          </div>
        </section>
        <div className="community-columns">
          <section className="community-feed" id="feed">
            <header>
              <h2>Today in the community</h2>
              <button>Latest⌄</button>
            </header>
            {blog.posts.map((post, index) => (
              <article>
                <span>{["AL", "MK", "JR"][index]}</span>
                <div>
                  <small>
                    {["Alex Lee", "Mika Khan", "June Rivera"][index]} ·{" "}
                    {post.meta}
                  </small>
                  <h3>
                    <a href={`/example/blog/${blog.slug}/demo/${post.slug}`}>
                      {post.title}
                    </a>
                  </h3>
                  <p>
                    {index
                      ? "A field note with references, rough edges, and room for a better answer."
                      : "Share the thing on your desk, in your garden, or in your editor right now."}
                  </p>
                  <footer>
                    <span>♡ {18 + index * 7}</span>
                    <a href={`/example/blog/${blog.slug}/demo/${post.slug}`}>
                      ▢ {6 + index * 3} replies
                    </a>
                  </footer>
                </div>
              </article>
            ))}
          </section>
          <aside className="community-topics" id="topics">
            <h2>Popular topics</h2>
            <a href="#topic">
              # work-in-progress <span>84</span>
            </a>
            <a href="#topic">
              # small-tools <span>61</span>
            </a>
            <a href="#topic">
              # local-groups <span>43</span>
            </a>
            <a href="#topic">
              # feedback <span>38</span>
            </a>
            <div>
              <strong>WEEKLY PROMPT</strong>
              <p>What did you remove this week?</p>
              <button>Join the prompt</button>
            </div>
          </aside>
        </div>
      </main>
    </div>
  );
}
src/components/examples/BlogArticles.tsx · shared articleTSX
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 "community":
src/components/examples/BlogExamples.tsx · communityTSX
slug: "community",
src/pages/example/blog/[slug]/demo.tsxTSX
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 })];
}
src/pages/example/blog/[slug]/demo/[post].tsxTSX
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 })];
}
examples/styles/base.cssCSS
.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;
  }
}
examples/styles/community.cssCSS
.tpl-community {
  color: #f1edf7;
  background: #12101b;
  font-family: Arial;
}
.tpl-community > header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: 78px;
  padding: 0 3vw;
  border-bottom: 1px solid #332d40;
}
.tpl-community > header > a {
  color: inherit;
  font-weight: 800;
}
.tpl-community > header > a i {
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: 8px;
  background: #9d67ff;
  border-radius: 50%;
}
.tpl-community > header nav a {
  color: #aaa1b8;
}
.tpl-community > header > div {
  justify-self: end;
}
.tpl-community button {
  padding: 9px 12px;
  color: #eee;
  border: 1px solid #40384f;
  background: #1a1722;
}
.tpl-community > header > div button:last-child {
  border-color: #9d67ff;
  background: #9d67ff;
}
.tpl-community > main {
  width: min(1160px, calc(100% - 40px));
  margin: auto;
}
.article-community .article-hero {
  background:
    radial-gradient(circle at 30% 40%, #9d67ff, transparent 22%),
    radial-gradient(circle at 70% 60%, #5d3d99, transparent 18%), #17141f;
}
.article-community article > header {
  text-align: center;
  margin: auto;
}
.article-community .article-content > aside {
  border-color: #9d67ff;
}
.community-hero {
  padding: 90px 0;
  text-align: center;
}
.community-faces {
  display: flex;
  justify-content: center;
}
.community-faces span {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-left: -8px;
  border: 3px solid #12101b;
  background: #9d67ff;
  border-radius: 50%;
  font: 9px var(--mono);
}
.community-hero h1 {
  margin: 30px 0;
  font-size: clamp(60px, 9vw, 110px);
  line-height: 0.85;
  letter-spacing: -0.075em;
}
.community-hero p {
  color: #aaa1b8;
  font-size: 18px;
}
.community-hero > div:last-child {
  color: #756c81;
}
.community-hero > div:last-child strong {
  color: #d9cff0;
}
.community-columns {
  display: grid;
  grid-template-columns: 1fr 310px;
  gap: 25px;
  padding-bottom: 90px;
}
.community-feed {
  border: 1px solid #332d40;
  background: #17141f;
}
.community-feed > header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px;
  border-bottom: 1px solid #332d40;
}
.community-feed > header h2 {
  margin: 0;
}
.community-feed > article {
  display: grid;
  grid-template-columns: 50px 1fr;
  gap: 15px;
  padding: 25px;
  border-bottom: 1px solid #332d40;
}
.community-feed > article > span {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  background: #9d67ff;
  border-radius: 50%;
  font: 9px var(--mono);
}
.community-feed small {
  color: #81778e;
}
.community-feed h3 {
  font-size: 23px;
}
.community-feed p {
  color: #aaa1b8;
}
.community-feed article footer {
  display: flex;
  gap: 20px;
  width: auto;
  padding: 0;
  border: 0;
  color: #81778e;
}
.community-topics {
  align-self: start;
  padding: 22px;
  border: 1px solid #332d40;
  background: #17141f;
}
.community-topics > a {
  display: flex;
  justify-content: space-between;
  padding: 13px 0;
  color: #c9bfd7;
  border-bottom: 1px solid #332d40;
}
.community-topics > div {
  margin-top: 25px;
  padding: 20px;
  background: #241c35;
}
.community-topics > div strong {
  color: #9d67ff;
  font: 9px var(--mono);
}
.article-author > span {
  display: grid;
  place-items: center;
  width: 55px;
  height: 55px;
  color: white !important;
  background: #9d67ff;
  border-radius: 50%;
}

@media (max-width: 800px) {
  .community-columns {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 480px) {
  .tpl-community > header {
    grid-template-columns: 1fr auto;
  }
  .tpl-community > header nav {
    display: none;
  }
  .tpl-community > header > div button:first-child {
    display: none;
  }
  .community-feed > article {
    grid-template-columns: 40px 1fr;
    padding: 18px;
  }
}