Dashboard chrome
Assemble the dashboard chrome from Enjab UI components instead of hand-rolling the sidebar, topbar, or table.
The dashboard chrome (sidebar, topbar, data table) is built from ready-made Enjab UI components. Never hand-roll these core pieces, reimplementing them causes visual drift across projects. Only the page content is custom.
Components
Build the entire chrome from these four:
- @enjab-ui/dashboard-shell: The dashboard layout. Fixed sidebar on the left, scrollable main column. Compose with sidebar and page-header.
- @enjab-ui/sidebar: The left navigation panel. Shows the tool's own brand (via
appName+appIcon, the same glyph as your favicon), not the Enjab logo. Grouped nav items with active state tracking. Account footer (avatar, email, sign-out) plus the "an Enjab product" byline pinned to the bottom. Plugs into Enjab Auth: passuser(name + email from getUser()) and a sign-out handler. - @enjab-ui/page-header: The topbar. Title, subtitle, and an action slot. Aligns with the sidebar brand header (same height, same bottom border).
- @enjab-ui/data-table: The dashboard table. Fully self-styled (bordered card, mono uppercase headers, comfortable rows, soft separators, canvas hover). Pass columns and rows; cell content stays flexible. Optional title and empty state included.
Assembly pattern
import { DashboardShell } from "@enjab-ui/dashboard-shell"
import { Sidebar } from "@enjab-ui/sidebar"
import { PageHeader } from "@enjab-ui/page-header"
export default function DashboardLayout({ children }) {
const user = await getUser() // Enjab Auth
return (
<DashboardShell>
<Sidebar
appName="Tool Name"
appIcon={<GlyphComponent />}
user={user}
onSignOut={handleSignOut}
>
<nav className="space-y-1.5">
{/* Your nav items */}
</nav>
</Sidebar>
<main>
<PageHeader
title="Page Title"
subtitle="Optional subtitle"
action={<Button>Action</Button>}
/>
{children}
</main>
</DashboardShell>
)
}App mark and favicon
The appIcon passed to sidebar must match your favicon. Store the glyph in one component to keep them in sync:
export function AppGlyph() {
return (
<svg viewBox="0 0 24 24" className="w-5 h-5">
{/* Your icon SVG markup */}
</svg>
)
}Use it in both the sidebar and favicon:
import { AppGlyph } from "./components/app-glyph"
const GLYPH = <AppGlyph />
// ... icon generation codeThe Enjab logo (@enjab-ui/logo) appears in exactly one place: the "an Enjab product" byline at the bottom of the sidebar. Never put it in the navbar or sidebar top.
Instant navigation
Every route that fetches data must ship a loading.tsx skeleton that mirrors the real page. Navigation must feel instant even while data loads. Never show a blank or frozen screen.
export default function DashboardLoading() {
return (
<div className="space-y-5 p-6">
<div className="h-10 w-48 animate-pulse bg-muted rounded" />
<div className="space-y-3">
<div className="h-12 w-full animate-pulse bg-muted rounded" />
<div className="h-12 w-full animate-pulse bg-muted rounded" />
</div>
</div>
)
}Skeletons use animate-pulse on bg-muted with the same layout as the real content. No layout shift when the real page loads.
Data table
Never build your own table. Use @enjab-ui/data-table with columns and rows:
import { DataTable } from "@enjab-ui/data-table"
export function PatientsTable({ patients }) {
const columns = [
{ header: "Name", accessor: "name" },
{ header: "ID", accessor: "id", className: "font-data" },
{ header: "Status", accessor: "status" },
]
return (
<DataTable
columns={columns}
rows={patients}
title="Patients"
/>
)
}The table handles styling, borders, hover states, and empty states. Cell content is yours to customize, but the outer structure is enforced across all Enjab dashboards.
Responsive behavior
The chrome is already phone-friendly:
- Sidebar: Collapses to a hamburger drawer below
lgbreakpoint. - Dashboard shell: Stacks on small screens.
- Data table: Scrolls horizontally inside its card, no data lost.
For your own page content, stack grids to one column on small screens (grid-cols-1 sm:grid-cols-2 lg:grid-cols-4), use responsive padding (p-4 sm:p-6), and avoid fixed pixel widths that overflow a phone.
Authentication wiring
The sidebar plugs into Enjab Auth. Pass the getUser() result and a sign-out handler:
Full auth integration
const user = await getUser() // Returns { name, email, id }
<Sidebar
user={user}
onSignOut={async () => {
"use server"
await signOut()
}}
/>Rules
- Never hand-roll the sidebar, topbar, or table. Use the components as-is.
- The brand header and topbar must align (same height, same bottom border).
- Every dashboard shows the "an Enjab product" byline at the bottom of the sidebar (via sidebar-footer, included in the sidebar component).
- Navigation items inside each group need spacing between them. Wrap items in
space-y-1orspace-y-1.5. - Every data-fetching route ships a loading.tsx skeleton. Never leave the page blank while data loads.
- The app's own brand mark (favicon + sidebar header) must be identical. Never let them drift.