@next-library/theme
v1.0.4
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/typographyQuick 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 wrapperNavigationBar- 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,
fetchRawFileContent,
createConfig,
type Locale,
} from '@next-library/core';
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 fetchRawFileContent(node.githubPath, 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 */
}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
