Technical Documentation

admin.ui

A production-grade admin UI prototype — built to demonstrate real-world architecture decisions, component design patterns, and engineering tradeoffs in a modern Next.js stack.

01

Technology stack

Every dependency is intentional — chosen for type safety, composability, or production credibility.

Framework

Next.js 16 (App Router)

Server Components by default. Route groups for layout isolation. No Pages Router.

Styling

Tailwind v4 + @theme inline

CSS-first config. Design tokens defined as raw CSS vars, mapped via @theme inline so utilities respond to runtime theme changes.

Component library

shadcn/ui (base-ui v4)

Headless primitives via @base-ui/react. No asChild on DropdownMenuTrigger — use onClick navigation or direct <Link> instead.

Charts

Recharts

Revenue area chart. Grid, tick, and activeDot colors use var(--color-*) refs so they respond to dark/light mode automatically.

Forms & validation

react-hook-form + Zod v4

zodResolver wires the schema to RHF. Zod v4 drops invalid_type_error — use per-rule messages instead.

Theme switching

next-themes

Writes class="dark" / class="light" to <html>. ThemeProvider wraps the entire tree in root layout.

Animations

tw-animate-css

Provides animate-in / fade-in / slide-in-from-* utilities used for page transitions and dropdown entrances.

Toast notifications

sonner

Mounted once in root layout via <Toaster />. Used for success/info/error feedback across all actions.

02

Architecture & route groups

Route groups isolate layout trees. The dashboard shell never touches auth pages.

File structure

src/app/
├── layout.tsx                 ← Root: providers only (ThemeProvider, Toaster)
├── globals.css                ← Design tokens + Tailwind v4 config
│
├── (dashboard)/               ← Route group — owns the shell
│   ├── layout.tsx             ← AppSidebar + AppHeader + PageTransition
│   ├── page.tsx               ← /  (Dashboard)
│   ├── loading.tsx            ← Skeleton fallback for all dashboard routes
│   ├── error.tsx              ← Error boundary for all dashboard routes
│   ├── invoices/
│   │   ├── page.tsx           ← /invoices
│   │   ├── new/page.tsx       ← /invoices/new
│   │   └── [id]/
│   │       ├── page.tsx       ← /invoices/:id
│   │       └── edit/page.tsx  ← /invoices/:id/edit
│   ├── customers/page.tsx     ← /customers
│   ├── billing/page.tsx       ← /billing
│   ├── settings/page.tsx      ← /settings
│   └── profile/page.tsx       ← /profile
│
├── (auth)/                    ← Route group — no shell
│   ├── layout.tsx             ← Passthrough (required to prevent layout leak)
│   ├── signin/page.tsx        ← /signin
│   └── documentation/
│       ├── page.tsx           ← /documentation (this page)
│       ├── collapsible-code.tsx
│       └── docs-nav.tsx
│
├── not-found.tsx              ← 404 (custom)
└── global-error.tsx           ← Root-level error (has own html/body)

Dashboard layout

Server

Owns the sidebar + header shell. Every dashboard route inherits this layout without re-mounting the shell on navigation.

Auth layout passthrough

Server

Without an explicit layout.tsx in (auth), Next.js walks up and applies (dashboard)/layout.tsx — rendering the sidebar on the login page. This file prevents that.

03

Design system

Two-layer token system: raw CSS vars per theme, then @theme inline maps them to Tailwind utilities.

Token architecture

Layer 1Raw CSS vars split by html.dark / html.light
Layer 2@theme inline maps utilities to var() refs
Resultbg-bg, text-fg, text-accent respond to theme toggle

Theme switching

Providernext-themes ThemeProvider in root layout
Attributeattribute="class" — writes .dark / .light
DefaultdefaultTheme="dark", enableSystem
Variant@custom-variant dark (&:is(.dark *))

Token reference

Token             Tailwind utility    Dark value          Light value
─────────────────────────────────────────────────────────────────────
--color-bg        bg-bg               #060912             #f8fafc
--color-bg-soft   bg-bg-soft          #0a0f1e             #f1f5f9
--color-surface   bg-surface          #0d1322             #ffffff
--color-surface-hi bg-surface-hi      #111827             #f1f5f9
--color-border    border-border       rgba(255,255,255,.08) rgba(0,0,0,.08)
--color-border-hi border-border-hi    rgba(96,165,250,.25) rgba(59,130,246,.35)
--color-fg        text-fg             #e8edf5             #0f172a
--color-fg-soft   text-fg-soft        #94a3b8             #475569
--color-fg-muted  text-fg-muted       #475569             #94a3b8
--color-accent    text-accent / bg-accent  #3b82f6        #3b82f6 (same)
--color-accent-hi text-accent-hi      #60a5fa             #2563eb
--color-success   text-success        #34d399             #10b981
--color-danger    text-danger         #f87171             #ef4444
--color-warning   text-warning        #fbbf24             #f59e0b

Status badge variants

PaidPendingDraftOverdueWarning

All variants are pure CSS — no hardcoded hex. They respond to dark/light mode via the token system.

04

Pages & routes

All dashboard routes are protected by the shell layout. Auth routes are public and layoutless.

Dashboard routes

Client
/KPI cards, revenue area chart (12-month), recent invoices table
/invoicesInvoice table — sort, filter by status, bulk select, pagination
/invoices/newNew invoice form with line-items editor and live summary
/invoices/[id]Invoice detail — read-only view with status timeline
/invoices/[id]/editEdit invoice — same form pre-filled from mock data
/customersCustomer table with sort, filter, avatar initials
/billingBilling overview — plan card, usage meters, payment history
/settingsTabbed settings: General, Team, Notifications, Danger Zone
/profileProfile tabs: Personal Info, Security (2FA), Activity log

Auth & public routes

Server
/signinSign-in page — email/password form + mock Google OAuth button
/documentationThis page — public, no auth guard, Server Component
/*not-found.tsx — custom 404 with Go Back + Dashboard actions

05

Custom UI components

Three custom form primitives that fill gaps in the shadcn/base-ui component set.

Client

ComboboxField

Searchable select built from Popover (@base-ui/react) + Command (cmdk). Width matches trigger via var(--anchor-width). Integrates with RHF via Controller. Used for: Customer select in invoice form, Timezone/Currency/Role in settings.

value: stringonChange: (v: string) => voidoptions: {value, label}[]placeholder?: string
Client

CurrencyInput

A dual-mode input: shows formatted $1,234.56 when blurred, switches to raw numeric string on focus for easy editing. Strips non-numeric characters on change. Uses inputMode="decimal" for mobile keyboards.

value: numberonChange: (n: number) => void
Client

DatePickerField

Calendar picker built on Popover + react-day-picker. Stores and emits values as YYYY-MM-DD strings using the local date constructor — avoids all UTC/timezone offset bugs that plague new Date(isoString).

value: stringonChange: (v: string) => voidplaceholder?: string

06

Invoice form & validation

RHF manages form state. Zod v4 validates fields. superRefine handles cross-field rules.

1
Client

Schema definition (Zod v4)

Each field has per-rule messages. Zod v4 removed invalid_type_error — messages live on individual refinements (min, max). superRefine runs after all fields pass and handles cross-field logic.

2
Client

Form setup

zodResolver bridges the schema to react-hook-form. defaultValues pre-fill edit mode from an existing invoice, or generate fresh defaults (invoice number, today's date, +14 days due) for new mode.

3
Client

Custom fields via Controller

ComboboxField, CurrencyInput, and DatePickerField don't use a native <input> ref, so they integrate via react-hook-form's Controller component instead of register().

4
Client

Error display pattern

Each field shows errors?.field?.message below the input with an AlertCircle icon. Warning state (non-blocking, e.g. tax > 30%) is shown in yellow text-warning without blocking submission.

Line items editor

Dynamic array managed with RHF useFieldArray. Each row has description, quantity, and unit price. The summary panel below reacts in real time — computing subtotal, applying tax rate and discount, then showing the final total. All math runs on the client with watched field values.

07

Layout system

Three client components build the persistent shell. Each has a single focused responsibility.

Client

AppSidebar

Collapsible sidebar (w-56 / w-16). Hidden on mobile — the header provides a slide-in drawer instead. Nav groups (Main / Finance / Settings) with labeled sections that collapse to icons-only mode. Uses style={{ zIndex: 10 }} — not Tailwind z-10 — because globals.css gives z-index: 1 to all aside elements outside any CSS layer.

Client

AppHeader

Sticky top bar with three zones: mobile menu button (hamburger, sm:hidden) → breadcrumb (auto-generated from pathname) → right actions (notifications bell + user menu). Notification dropdown tracks unread count with per-item mark-as-read and mark-all-read. User menu logout button calls router.push('/signin').

Client

PageTransition

Wraps every page inside <main>. Uses key={pathname} to force React to remount the div on each navigation, re-triggering the CSS entry animation. Intentionally has no h-full — that would collapse main's bottom padding on scrollable pages.

08

Mobile & responsiveness

Mobile-first breakpoints. Tables become card lists. Sidebar becomes a slide-in drawer.

Table → card pattern

Both the invoice table and customer table use the same pattern: a standard <table> with hidden sm:table and a card list with sm:hidden. Zero JS — pure CSS breakpoints.

Responsive rules

Sidebarhidden sm:flex — mobile uses header drawer
Nav linkshidden sm:flex — mobile uses DocsNav hamburger
Tablehidden sm:table / sm:hidden card view
Gridsgrid-cols-1 → sm:grid-cols-2
Hero h1text-3xl → sm:text-4xl
Page paddingpx-4 py-10 → sm:px-6 sm:py-16
Section gapgap-14 → sm:gap-20

Overflow prevention checklist

Grid cards containing <pre> blocks: add min-w-0 overflow-hidden to the card div
Static code <pre>: always w-full overflow-x-auto to scroll inside the card
Inline <code> in flex rows: min-w-0 break-all prevents long monospace strings from overflowing
Page titles in PageHeader: min-w-0 truncate prevents long route names from pushing actions off-screen
Sidebar: min-w-0 on the content column prevents text overflow when collapsed

z-index caveat

globals.css sets z-index: 1 on main, header, footer, section, asideoutside any CSS layer. This silently overrides Tailwind's z-N utilities on those elements. Fix: always use style={{ zIndex: N }} for sticky navs, dropdowns, and sidebars — never Tailwind z-classes on these element types.

09

Error handling

Four layers of error handling — from route-level boundaries to the root fallback.

1
Client

Route segment error boundary — error.tsx

Catches runtime errors thrown inside the (dashboard) route segment. Provides Try again (calls reset() to re-render), Go Back (router.back()), and Dashboard (href='/') actions. Logs error to console for monitoring. Shows digest ID when available.

2
Server

Loading skeleton — loading.tsx

Next.js shows this immediately while a route segment suspends during navigation. Renders a grid of skeleton cards that match the dashboard layout shape, reducing perceived load time.

3
Server

Custom 404 — not-found.tsx

Rendered when notFound() is called or a route has no match. Has a Go Back button and a link back to the dashboard so users are never stranded.

4
Client

Root-level error — global-error.tsx

Catches errors that escape all route boundaries — including errors inside root layout.tsx. Must include its own <html> and <body> tags since the normal layout is unavailable. Provides a simple Reload button.

Error layer summary

FileScopeType
(dashboard)/error.tsxAll dashboard routesClient error boundary
(dashboard)/loading.tsxAll dashboard routesSuspense skeleton
not-found.tsxAll routesServer 404
global-error.tsxRoot layout + aboveClient root fallback