React Components

React components for your blog, changelog and help center

Updated 43 views

@kookee/react includes ready-made components for the public pages every product ends up building: a blog, a changelog, a help center and a read-only roadmap. It also covers the pieces around them: filters, pagination, a table of contents, reactions, a "Was this article helpful?" vote, a what's-new badge, search as you type and an announcement pill with its list.

They work the other way round from the hooks. A hook fetches data for you and leaves the markup to you. A component takes data you already fetched with @kookee/sdk as props and renders real HTML, on the server as well as in the browser. There is no provider to mount, no loading state to handle, and no request from the browser for anything that does not need one.

Kookee's own blog, changelog, help center and roadmap, including the page you are reading, are built with them.

Install

Bash
npm install @kookee/react @kookee/sdk

The components need version 1.9.0 or later of both packages, and React 18 or 19. A few later additions, such as RoadmapCategoryFilter, search highlighting and the collapsible tag filter, need 1.9.3.

Import the two stylesheets once, at the root of your app:

ts
import '@kookee/react/content.css'; // the components
import '@kookee/sdk/styles/content.css'; // code blocks and file chips inside entry bodies

Leave the first one out and the components render unstyled, with every class name and data-* attribute still in place, which is what you want if you would rather write all of the CSS yourself. In a Tailwind v4 app the import order matters; see Style and customize the React components.

@kookee/react/content.css also styles the text of the entry bodies the components render: headings, links, lists, quotes, code, images and tables in BlogPost, ChangelogArticle, HelpArticle, AnnouncementArticle and the updates of StatusIncidentArticle. Those bodies carry the kookee-typography class, so leave Tailwind's prose off them. A standalone EntryContent keeps your own typography; give it className="kookee-typography" for the same styles. If you render an entry body without these components and have no typography styles of your own, add @kookee/sdk/styles/typography.css.

The examples authenticate with your project's API key, from Connect → API keys. The interactive components send it from the visitor's browser, which is what the key is built for, so add your site's domains to the project's Allowed Origins. If the project's hosted portal is enabled, you can pass projectId instead of the key; without the portal, a request with only a project id is refused.

A first page

A changelog page in the Next.js App Router, rendered entirely on the server:

tsx
// app/changelog/page.tsx
import { Kookee } from '@kookee/sdk';
import { ChangelogList } from '@kookee/react/changelog';

const kookee = new Kookee({ apiKey: process.env.KOOKEE_API_KEY! });

export default async function ChangelogPage() {
  const { data } = await kookee.changelog.list({ limit: 20 });
  return <ChangelogList entries={data} href="/changelog/{slug}" />;
}

That is a dated list of entries with type badges, versions, titles, summaries and cover images, each linked to /changelog/<slug>. The browser receives HTML and no JavaScript for it.

The same component works in any React app. In TanStack Start, fetch in the route loader. In a single-page app built with Vite, pass it the data of a hook such as useChangelog(). Using the React components with Next.js, TanStack Start or Vite walks through all three.

Server and client import paths

Every area renders from @kookee/react/<area>, and the areas with interactive pieces have a second path:

  • @kookee/react/<area> renders on the server, and on the client too. It uses no state and no browser APIs, so it is safe in a React Server Component.

  • @kookee/react/<area>/client, for entry, changelog, help and announcements, holds the interactive pieces, marked 'use client'. Each component there has a headless hook behind it, so you can keep the behaviour and write your own markup. The announcements path holds only a hook, useDismissedAnnouncements.

Import

Runs on

Components

@kookee/react/entry

server and client

EntryContent, TableOfContents, Pagination, Highlight

@kookee/react/entry/client

client

Reactions, TrackedTableOfContents

@kookee/react/blog

server and client

BlogPostList, BlogPostCard, BlogPost, BlogTagFilter, BlogPostListSkeleton

@kookee/react/changelog

server and client

ChangelogList, ChangelogItem, ChangelogArticle, ChangelogTypeFilter, ChangelogListSkeleton

@kookee/react/changelog/client

client

WhatsNew

@kookee/react/help

server and client

HelpCategoryGrid, HelpCategoryCard, HelpArticleList, HelpArticleCard, HelpArticle, HelpCategoryHeader, HelpCategoryNav, HelpBreadcrumbs, HelpCategoryGridSkeleton, HelpArticleListSkeleton

@kookee/react/help/client

client

HelpSearchBox, HelpfulVote

@kookee/react/roadmap

server and client

Roadmap, RoadmapColumn, RoadmapPostCard, RoadmapCategoryFilter, RoadmapSkeleton

@kookee/react/status

server and client

StatusHeadline, StatusOverview, StatusIncidentArticle, StatusBadge

@kookee/react/announcements

server and client

AnnouncementPill, AnnouncementList, AnnouncementArticle

@kookee/react/announcements/client

client

useDismissedAnnouncements

The blog, changelog, help and roadmap areas also export the smaller parts their components are built from, such as BlogPostTitle or ChangelogTypeBadge, so a custom layout can reuse them. The logic worth reusing is exported as plain functions (extractHeadings, getPaginationItems, groupRoadmapPosts, snippetAround) and as hooks (useReactions, useActiveHeading, useWhatsNew, useHelpSearchBox).

@kookee/react/status renders a status page from what kookee.status.get() and kookee.status.getIncident(id) return: the overall state, incidents, maintenance and each component's uptime over 90 days. The status page your hosted portal shows is built from it too. It needs version 1.19.0 of both packages; see Show your status page on your own site.

@kookee/react/announcements renders your announcements as a pill to place under your header, a list and one announcement in full. The dialog or page they open in is your app's, and useDismissedAnnouncements keeps what each visitor dismissed. The pieces render on the server too, but onOpen, onSelect and onDismiss are functions, so the component that passes them is a client component. The area needs version 1.20.0 of both packages; see Show announcements in your React app.

The package root, @kookee/react, with its provider and hooks, is a client module, and so is @kookee/react/chat. In a Server Component, import EntryContent from @kookee/react/entry rather than from the root: imported from the root it becomes a client component, and Next.js sends the article body to the browser twice, once as HTML and once in the component's props.

Every component that links somewhere takes href, either as a template or as a function:

tsx
<BlogPostList posts={posts} href="/blog/{slug}" />
<BlogPostList posts={posts} href={(post) => `/${post.locale}/blog/${post.slug}`} />

Prop

Placeholders

entry links: blog posts, changelog entries, help articles, what's new, search results

{slug}, {id}, {categorySlug}

tagHref, and href on BlogTagFilter

{tagSlug}

help category links and categoryHref on HelpBreadcrumbs

{categorySlug}, {categoryId}

href on ChangelogTypeFilter

{type}

href on RoadmapCategoryFilter

{category}

href on Pagination

{page}

roadmap card href

{id}

incidentHref on StatusOverview

{id}

Values are URL-encoded. When a placeholder has no value, for example a help article without a category, or when your function returns null, the item renders without a link instead of pointing at /help/undefined. Cards and changelog items also get data-has-link="false".

renderLink replaces the default <a> with your router's link component. It receives href, className and children, plus aria-current, aria-label and rel where they apply; spread them onto your link:

tsx
import Link from 'next/link';

<BlogPostList posts={posts} href="/blog/{slug}" renderLink={(props) => <Link {...props} />} />;

TanStack Router needs a small adapter, because its to prop drops a query string; see the framework article.

Filters and pagination are links, not buttons with state. BlogTagFilter, ChangelogTypeFilter, RoadmapCategoryFilter and Pagination point at a URL, and your page renders again for it. That is why they work from a Server Component and before any JavaScript has loaded, and why every filtered view has an address you can share and a search engine can crawl.

Five components need the browser:

  • Reactions shows emoji reactions with their counts, updates them the moment a visitor clicks, and remembers in localStorage what that visitor pressed.

  • HelpfulVote asks "Was this article helpful?" with yes and no.

  • TrackedTableOfContents highlights the heading being read.

  • WhatsNew shows how many changelog entries were published since the visitor last looked, and lists them in a popover.

  • HelpSearchBox searches your help articles as the visitor types.

None of them needs a provider. The ones that call the API, Reactions, HelpfulVote and HelpSearchBox, take the same config you would give KookeeProvider: { apiKey }, or { projectId } with the portal enabled.

Where to go next

  • Build a blog with the React components

  • Build a changelog with the React components

  • Build a help center with the React components

  • Show a read-only roadmap with the React components

  • Style and customize the React components

  • Using the React components with Next.js, TanStack Start or Vite

If something looks wrong, Fixing problems with the React components covers the usual causes.

Continue reading

Was this article helpful?