@next-library/theme
v1.3.0
Published
Complete UI theme for Next.js documentation framework
Maintainers
Readme
@next-library/theme
Complete UI theme package for Next.js documentation framework. This package provides ready-made React components built on top of @next-library/core.
Looking for core utilities only? See
@next-library/core.
Content tree: Layout and nav expect the same
PageDatafromextractPageData(contentTree, locale, slug). ImportcontentTreefrom your generatedlib/tree.tsand passcontentTreeintoNavigationBar(and derivegetSupportedLocales(contentTree)forLanguageSwitcher). The canonical wiring isexample/app/anddocs/getting-started.md.
Features
- 🎨 Complete UI Components - Layout, navigation, markdown rendering, and more
- 🌍 Multi-language Support - Built-in translations for 20+ languages
- 📱 Responsive Design - Mobile-first with drawer navigation and responsive breadcrumbs
- 🎯 Type-Safe - Full TypeScript support
- ⚡ Server/Client Separation - Optimized for Next.js App Router
- 🎭 Theme Support - Dark mode with
next-themes - 📝 Rich Markdown - Syntax highlighting, math, diagrams, callouts
Installation
# Using npm
npm install @next-library/theme @next-library/core
# Using pnpm
pnpm add @next-library/theme @next-library/core
# Using yarn
yarn add @next-library/theme @next-library/coreRequired peer dependencies:
next-themes- For theme switching (dark/light mode)@tailwindcss/typography- For markdown prose styling
pnpm add next-themes @tailwindcss/typographyModule format (ESM vs @next-library/core)
@next-library/theme is published as ESM-only (import / export in package.json exports). Use it from Next.js App Router (or any bundler that resolves ESM) as in this repo’s example app.
@next-library/core ships both ESM and CommonJS (import and require via exports), which helps generic Node tooling and older setups. You do not need CJS to use the theme with Next.js; if you rely on require('@next-library/theme') in plain Node without a bundler, prefer importing @next-library/core for config/tree utilities and keep UI imports inside the Next.js bundle.
Quick Start
1. Set Up Tailwind CSS v4
The theme package uses Tailwind CSS v4 with @source directives (Nextra-style approach). Create or update your globals.css:
/* app/globals.css */
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
/* Import theme package styles to scan for Tailwind classes */
@import '@next-library/theme/style';
@custom-variant dark (&:is(.dark *));
:root {
/* Your CSS variables */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
/* ... more variables */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
/* ... more variables */
}Important: The @import '@next-library/theme/style' line tells Tailwind where to scan for classes used in the theme components.
2. Set Up Theme Provider
Create a theme provider wrapper:
// app/providers.tsx
'use client';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { ReactNode } from 'react';
export function ThemeProvider({ children }: { children: ReactNode }) {
return (
<NextThemesProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</NextThemesProvider>
);
}Wrap your app in app/layout.tsx:
// app/layout.tsx
import { ThemeProvider } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}3. Use Components
Server Components
Import server components from @next-library/theme/server:
// app/[locale]/layout.tsx
import { DocumentationLayout } from '@next-library/theme/server';
import { isValidLocale, type Locale } from '@next-library/core';
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale: raw } = await params;
const locale = (isValidLocale(raw) ? raw : 'en') as Locale;
return (
<DocumentationLayout language={locale}>
{children}
</DocumentationLayout>
);
}Client Components
Import client components from @next-library/theme/client:
Server parent (derive locales from the generated tree):
import { getSupportedLocales } from '@next-library/core';
import contentTree from '@/lib/tree';
import { LocaleTools } from './locale-tools';
const availableLocales = getSupportedLocales(contentTree);
// …
<LocaleTools availableLocales={availableLocales} />;Client component:
// app/locale-tools.tsx
'use client';
import { LanguageSwitcher, ThemeSwitcher } from '@next-library/theme/client';
export function LocaleTools({ availableLocales }: { availableLocales: string[] }) {
return (
<div>
<LanguageSwitcher availableLocales={availableLocales} />
<ThemeSwitcher />
</div>
);
}NavigationBar computes locales from contentTree when you pass contentTree (see complete example below).
Component Exports
Server Components (@next-library/theme/server)
DocumentationLayout- Main layout wrapper (passcookieBanner={false}to hide the cookie consent banner)NavigationBar- Top navigation barLeftSidebar- Left sidebar navigationRightSidebar- Right sidebar (TOC, contributors)DocumentationFooter- Footer componentStructuredData- JSON-LD structured data renderer
Client Components (@next-library/theme/client)
Navigation:
Banner- Top banner componentLogo- Logo componentTopCategories- Top category navigationSearchBar- Search input with keyboard shortcutsThemeSwitcher- Dark/light theme toggleLanguageSwitcher- Language selection dropdownMobileMenu- Mobile navigation drawerGithubStar- GitHub star buttonGithubLink- GitHub repository linkBreadcrumbs- Breadcrumb navigationBreadcrumbToggle- Responsive breadcrumb overflowFolderCards- Grid of folder cardsSidebarNavigation- Sidebar navigation treeTableOfContents- Table of contentsGithubContributors- Contributors listLevelElevator- Current level indicatorAnchorLinkHandler- Anchor link copying
Markdown:
MarkdownRenderer- Markdown content rendererCopyPageButton- Copy page content button with ChatGPT/Claude integrationPageHeader- Page title with copy buttonFallbackLocaleWarning- Warning for fallback locale content
UI Primitives:
Button,Card,Avatar,Collapsible,Drawer,DropdownMenu,Select,Separator,Sheet,Tooltip,Input,Skeleton- Sidebar components (
SidebarProvider,SidebarContent,SidebarMenu, etc.) - Breadcrumb components (
Breadcrumb,BreadcrumbItem, etc.)
Tracking:
CookieBanner- Cookie consent banner
Hooks:
useMediaQuery- Media query hookuseIsMobile- Mobile detection hook
Complete Example
Layout Structure
// app/[locale]/layout.tsx
import { DocumentationLayout } from '@next-library/theme/server';
import { isValidLocale, type Locale } from '@next-library/core';
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale: rawLocale } = await params;
const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;
return (
<DocumentationLayout language={locale}>
{children}
</DocumentationLayout>
);
}Documentation Page Layout
// app/[locale]/docs/[[...slug]]/layout.tsx
import { NavigationBar, LeftSidebar, RightSidebar } from '@next-library/theme/server';
import { Breadcrumbs, AnchorLinkHandler } from '@next-library/theme/client';
import { extractPageData, isValidLocale, createConfig, type Locale } from '@next-library/core';
import { libraryConfig } from '@/library.config';
import contentTree from '@/lib/tree';
export default async function DocsLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string; slug?: string[] }>;
}) {
const { locale: rawLocale, slug } = await params;
const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;
const safeSlug = Array.isArray(slug) ? slug : [];
const fullConfig = createConfig(libraryConfig);
const pageData = extractPageData(contentTree, locale, safeSlug);
const repoUrl =
fullConfig.github?.user && fullConfig.github?.repo
? `https://github.com/${fullConfig.github.user}/${fullConfig.github.repo}`
: undefined;
return (
<div>
<NavigationBar
language={locale}
className="block lg:hidden"
pageData={pageData}
websiteTitle={fullConfig.website?.title || 'Documentation'}
contentTree={contentTree}
repoUrl={repoUrl}
/>
<div className="flex flex-row gap-12 w-full lg:w-[95vw] mx-auto pt-10">
<LeftSidebar
data={pageData}
className="sticky top-28 hidden lg:flex lg:flex-[2] max-w-xs"
/>
<main className="flex flex-col lg:gap-8 gap-4 flex-1 w-full lg:flex-[6] px-4 lg:px-0">
<Breadcrumbs data={pageData} locale={locale} />
{children}
</main>
<RightSidebar
data={pageData}
className="sticky top-28 hidden lg:flex lg:flex-[1.5] max-w-xs"
/>
<AnchorLinkHandler locale={locale} />
</div>
</div>
);
}Documentation Page
// app/[locale]/docs/[[...slug]]/page.tsx (leaf page — folder views need FolderCards; see example app)
import {
extractPageData,
isValidLocale,
createConfig,
type Locale,
} from '@next-library/core';
import { loadDocumentationMarkdown } from '@next-library/core/server';
import { PageHeader, FallbackLocaleWarning } from '@next-library/theme/server';
import { MarkdownRenderer } from '@next-library/theme/client';
import { libraryConfig } from '@/library.config';
import contentTree from '@/lib/tree';
import { notFound } from 'next/navigation';
export default async function DocPage({
params,
}: {
params: Promise<{ locale: string; slug?: string[] }>;
}) {
const { locale: rawLocale, slug } = await params;
const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;
const safeSlug = Array.isArray(slug) ? slug : [];
const fullConfig = createConfig(libraryConfig);
const pageData = extractPageData(contentTree, locale, safeSlug);
const { node } = pageData;
if (!node?.githubPath || !fullConfig.github?.user || !fullConfig.github?.repo) {
notFound();
}
const githubConfig = {
user: fullConfig.github.user,
repo: fullConfig.github.repo,
branch: fullConfig.github.branch || 'main',
token: fullConfig.github.token,
app: fullConfig.github.app,
};
const rawMarkdown = await loadDocumentationMarkdown(node.githubPath, {
contentDir: fullConfig.tree?.contentBodiesDir,
github: githubConfig,
});
const baseUrl = fullConfig.website?.siteUrl || 'http://localhost:3000';
const pageUrl = `${baseUrl.replace(/\/$/, '')}/${locale}/docs/${safeSlug.join('/')}`;
const websiteTitle = fullConfig.website?.title || fullConfig.website?.organizationName || 'Documentation';
return (
<div>
<PageHeader
title={node.title}
markdown={rawMarkdown}
pageUrl={pageUrl}
websiteTitle={websiteTitle}
locale={locale}
/>
{node.fallback && <FallbackLocaleWarning locale={locale} />}
<MarkdownRenderer markdown={rawMarkdown} />
</div>
);
}For JSON-LD, generateMetadata, folder index views (FolderCards), and error handling, copy from example/app/[locale]/docs/[[...slug]]/page.tsx in the monorepo.
Internationalization
The theme package includes translations for 20+ languages. Components automatically use translations based on the locale prop:
import { CopyPageButton } from '@next-library/theme/client';
// Uses default translations for locale
<CopyPageButton
sourceCode={markdown}
locale="fr" // French translations will be used
/>
// Override specific translations
<CopyPageButton
sourceCode={markdown}
locale="fr"
translations={{
copyPage: 'Copier la page',
copied: 'Copié',
}}
/>Supported Locales
- English (en)
- French (fr)
- German (de)
- Spanish (es)
- Arabic (ar) - RTL support
- Chinese (zh)
- Japanese (ja)
- Korean (ko)
- Portuguese (pt)
- Italian (it)
- Russian (ru)
- Dutch (nl)
- Swedish (sv)
- Norwegian (no)
- Danish (da)
- Finnish (fi)
- Polish (pl)
- Turkish (tr)
- Hebrew (he) - RTL support
- Hindi (hi)
Custom Translations
All components accept an optional translations prop to override default translations:
interface ComponentTranslations {
// Component-specific translation keys
copyPage?: string;
copied?: string;
// ... more keys
}Styling
Tailwind CSS v4
The theme uses Tailwind CSS v4 with @source directives. Make sure to import the theme style file in your globals.css:
@import '@next-library/theme/style';This tells Tailwind where to scan for classes used in theme components.
CSS Variables
The theme uses CSS variables for theming. Define your variables in globals.css:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
/* ... more variables */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
/* ... more variables */
}Optional design tokens (1.1)
Every component reads the semantic tokens above. A site can go further and restyle the whole theme (header, tree, search, TOC, code cards, callouts, prose) by defining optional tokens. Each one has a fallback, so a site that defines none keeps a neutral look:
| Token | Used for | Fallback |
|---|---|---|
| --card-2 | top of the panel gradient | --card |
| --glass, --glass-2 | pills, hovers, active rows | foreground at 3% / 6% |
| --faint | labels, crumbs, TOC idle | --muted-foreground |
| --edge, --shadow-xl | glass overlays | neutral |
| --jewel, --jewel-text, --jewel-top, --jewel-bottom, --jewel-on | rails, rings, level tile, link underline | --foreground / --background |
| --nl-font-heading, --nl-heading-weight | page title, prose h1 and h2 | inherit, 600 |
| --nl-font-mono | labels, crumbs, meta, code | ui-monospace |
| --nl-prose-size, --nl-prose-leading | article body | 1rem, 1.75 |
| --nl-content-width | header row max width | full width |
| --nl-code-bg, --nl-code-fg, --nl-code-muted, --nl-code-line, --nl-code-keyword, --nl-code-string, ... | code cards and syntax colors | --card, One Light / One Dark |
| --nl-callout-note, --nl-callout-tip, --nl-callout-warning, --nl-callout-danger, --nl-callout-important | callout accents | blue, green, amber, red, purple |
| --nl-star | GitHub star mark | gold |
Section dots: any element with data-section="<top-level slug>" paints its
.nl-dot with --nl-dot, so a site can color its sections:
[data-section="offensive"] { --nl-dot: #f87171; }New props: DocumentationLayout and NavigationBar accept logo (a brand
mark node), navLinks (header links instead of the top categories),
searchPlaceholder and preferencesLabels; DocumentationLayout also accepts
mobileNavbar (show the root header on small screens; docs pages that render
their own mobile navbar mark it nl-docs-nav). PageHeader accepts
description, meta (items of the meta strip) and actions (extra page
action chips). PreferencesMenu (client) is one header button with the theme
swatches (every theme registered with next-themes) and the language list; the
navbar uses it instead of ThemeSwitcher and LanguageSwitcher, which stay
exported. A tab group whose panels are all single code blocks renders as one
code card with the tab names in its bar.
Customization
You can customize component styles by:
- Overriding CSS variables - Change theme colors via CSS variables
- Using className prop - Most components accept
classNamefor additional styling - Tailwind classes - All components use Tailwind, so you can override with utility classes
Component Props
DocumentationLayout
interface DocumentationLayoutProps {
children: React.ReactNode;
language?: Locale;
bannerMessage?: React.ReactNode | null;
websiteTitle?: string;
}NavigationBar
interface NavigationBarProps {
language?: Locale;
className?: string;
pageData?: PageData;
websiteTitle?: string;
contentTree?: ContentTree;
repoUrl?: string;
}PageHeader
interface PageHeaderProps {
title: string;
markdown: string;
pageUrl?: string;
websiteTitle?: string;
className?: string;
locale?: Locale;
translations?: CopyPageButtonTranslations;
showCopyButton?: boolean;
}CopyPageButton
interface CopyPageButtonProps {
sourceCode: string;
pageUrl?: string;
websiteTitle?: string;
className?: string;
locale?: Locale;
translations?: CopyPageButtonTranslations;
}The CopyPageButton includes integration with ChatGPT and Claude:
- Copy page - Copies markdown content to clipboard
- Open in ChatGPT - Opens ChatGPT with prefilled message including website title and URL
- Open in Claude - Opens Claude with prefilled message including website title and URL
Server/Client Separation
The theme package properly separates server and client components:
- Server components are imported from
@next-library/theme/server - Client components are imported from
@next-library/theme/client - Server components can import client components (they're externalized in the build)
This ensures optimal performance with Next.js App Router.
TypeScript Support
The package is fully typed. Import types as needed:
import type {
DocumentationLayoutProps,
NavigationBarProps,
PageHeaderProps,
CopyPageButtonProps,
} from '@next-library/theme/server';Examples
See the example directory for a complete working example.
API Reference
Server Components
DocumentationLayout- Main layout wrapperNavigationBar- Top navigationLeftSidebar- Left sidebar navigationRightSidebar- Right sidebar (TOC, contributors)DocumentationFooter- FooterStructuredData- JSON-LD renderer
Client Components
See the Component Exports section above for the complete list.
Contributing
Contributions are welcome! Please see our contributing guide for details.
License
MIT
