NNuartz

Creating Layout Components

Updated 2026-03-193 min read

nuartz's UI is built from standard Next.js React components using Tailwind CSS v4. This guide explains how to add new components or extend existing ones.

Component Location

All layout and UI components live in apps/web/components/. They are organized by concern:

apps/web/components/
├── layout/
│   ├── nav-sidebar.tsx      # Left navigation tree
│   ├── right-sidebar.tsx    # Right sidebar (TOC, graph, backlinks)
│   └── breadcrumbs.tsx      # Breadcrumb navigation
├── graph-view.tsx           # D3 force-directed graph
├── backlinks.tsx            # Backlinks list
├── popover-preview.tsx      # Hover preview for wikilinks
├── reader-mode-toggle.tsx   # Reader mode button
└── copy-code.tsx            # Code block copy button

A Simple Component

Create a new file in apps/web/components/:

interface NoteCountProps {
  count: number
}
 
export function NoteCount({ count }: NoteCountProps) {
  return (
    <p className="text-xs text-muted-foreground">
      {count} notes published
    </p>
  )
}

Then import and use it in a page or layout.

Adding to the Page Layout

The main page layout is in apps/web/app/[...slug]/page.tsx. The right sidebar content is composed in:

import { NoteCount } from "@/components/note-count"
 
// Inside the sidebar section:
<NoteCount count={files.length} />

Client vs Server Components

Next.js 15 uses React Server Components by default. Most layout components can be server components (no "use client" directive needed).

Use "use client" only when you need:

  • Browser APIs (window, document, localStorage)
  • React hooks (useState, useEffect, useRef)
  • Event listeners
"use client"
 
import { useState } from "react"
 
export function Collapsible({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false)
  return (
    <div>
      <button onClick={() => setOpen(o => !o)}>Toggle</button>
      {open && children}
    </div>
  )
}

Styling

nuartz uses Tailwind CSS v4 with oklch-based CSS variables defined in apps/web/app/globals.css. Use the standard Tailwind utility classes and design tokens:

TokenPurpose
text-foregroundPrimary text
text-muted-foregroundSecondary / dimmed text
bg-backgroundPage background
bg-mutedSubtle surface (hover states, cards)
borderDefault border colour
hsl(var(--primary))Accent colour
Warning

Tailwind v4's @apply only works inside files that begin with @import "tailwindcss". For component-level styles, use utility classes directly in JSX.

Reading Markdown Data

Server components can read content from the filesystem using nuartz's core functions:

import { getAllMarkdownFiles, renderMarkdown } from "nuartz"
import path from "node:path"
 
const CONTENT_DIR = path.join(process.cwd(), "content")
 
export async function RecentNotes() {
  const files = await getAllMarkdownFiles(CONTENT_DIR)
  const recent = files
    .sort((a, b) => (b.frontmatter.date ?? "") > (a.frontmatter.date ?? "") ? 1 : -1)
    .slice(0, 5)
 
  return (
    <ul>
      {recent.map(f => (
        <li key={f.slug}>
          <a href={`/${f.slug}`}>{f.frontmatter.title ?? f.slug}</a>
        </li>
      ))}
    </ul>
  )
}

Tips

  • Keep components small and focused — one responsibility per file.
  • Prefer server components to avoid shipping unnecessary JavaScript to the browser.
  • Look at existing components in apps/web/components/ as reference implementations.