G Gentelella v4.0.0

Adding a Page

Add a new page to Gentelella in two steps. Drop an HTML file in production/ — Vite auto-discovers it — then add one entry to the NAV array so it shows up in the sidebar and ⌘K command palette.

Last updated May 22, 2026

The Vite multi-page setup is designed so that adding a page is two small edits. Vite auto-discovers every production/*.html file as a Rollup entry — no manual config — and the shell renders from a single NAV array.

Let’s add a hypothetical “Reports” page.

1. Create the HTML file

Drop a new file in production/. The fastest way is to copy production/plain_page.html — it’s the canonical blank starter — then change the title and body attributes:

<!-- production/reports.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Reports | Gentelella 2026 v4</title>
  <link rel="icon" href="../images/favicon.svg" type="image/svg+xml">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  <script type="module" src="/src/main-v4.js"></script>
</head>
<body data-shell="admin" data-page="reports" data-breadcrumb="Home > Reports">

  <main class="main">
    <div class="page-wrapper">
      <div class="page-header">
        <div class="page-header-row">
          <div>
            <div class="pretitle">Q2 2026</div>
            <h1 class="page-title">Reports</h1>
          </div>
        </div>
      </div>

      <div class="card">
        <div class="card-header">
          <h3 class="card-title">Your content here</h3>
        </div>
        <div class="card-body">
          <!-- your page content -->
        </div>
      </div>
    </div>
  </main>

</body>
</html>

What the body attributes do

  • data-shell="admin" — opt-in. Gets you the sidebar, topbar, and footer injected at build time by vite.config.js. Skip it for auth or marketing pages that want a different layout.
  • data-page="reports" — must match the key you’ll add to NAV in step 2. The sidebar uses this to highlight the active item.
  • data-breadcrumb="Home > Reports">-separated string. Last segment becomes the current page label.

Note that pages live in production/, not src/. That’s a quirk from the legacy Gentelella layout, kept for backwards compatibility — assets in src/ get bundled by Vite; HTML in production/ is the entry-point root.

2. Add the nav entry

Open src/v4/shell-render.js and find the NAV array. Add a new item to whichever section makes sense — let’s say the General group:

export const NAV = [
  {
    label: 'General',
    items: [
      {
        text: 'Dashboards', icon: 'dashboard',
        children: [
          { key: 'dashboard',   href: 'index.html',  text: 'Operations' },
          // ...
        ]
      },
      // Add this:
      { key: 'reports', href: 'reports.html', text: 'Reports', icon: 'pages' },
      // ...
    ]
  },
  // ...
];

The required fields are key (matches your page’s data-page), href (the HTML file in production/), text (sidebar label), and icon (a string key into the ICONS map further down in shell-render.js).

Available icons

The ICONS object near the bottom of shell-render.js is a name → SVG path map. Common ones: dashboard, forms, tables, charts, calendar, map, chat, mail, kanban, files, shop, tag, cart, users, profile, settings, pages, layout, code, paint, type, icons, media, help, bell.

To add a custom icon: append a new entry to the ICONS map with the inner <path> markup. Lucide and Tabler icons work well — 24×24 viewBox, stroked, fill: none.

Optional fields

  • badge: { text: 'New', cls: 'badge-teal' } — pill next to the label. Three badge classes are defined in _layout.scss: badge-teal, badge-blue, badge-red. Add more by extending the .badge rule with new color modifiers if you need them.
  • children: [...] — turns the item into a parent group with a submenu. Children are flat { key, href, text } objects without their own icons. The parent stays expanded while any child is the active page.
{
  text: 'Reports', icon: 'pages',
  children: [
    { key: 'reports-sales',   href: 'reports_sales.html',   text: 'Sales' },
    { key: 'reports-traffic', href: 'reports_traffic.html', text: 'Traffic' }
  ]
},

3. Restart Vite

Vite scans production/ for entry HTML files only at startup. Stop npm run dev (Ctrl+C) and run it again:

npm run dev

Visit http://localhost:9173/production/reports.html — the page renders with the sidebar, topbar, footer, theme toggle, dark mode, and ⌘K palette all wired up. The Reports nav item is highlighted, and the topbar shows “Home > Reports”.

What you didn’t have to do

  • No router config — pages are static HTML, served by Vite as separate entries
  • No manual rollupOptions.input registration — discoverEntries() in vite.config.js reads production/ and registers every .html automatically
  • No shell template duplication — the build-time plugin injects the sidebar/topbar/footer for any page with data-shell="admin"
  • No CSS imports — main-v4.js imports main.scss, every page links to the same compiled bundle

Removing a page

Just the reverse:

  1. Delete production/reports.html
  2. Remove the entry from NAV in shell-render.js
  3. Restart the dev server (so Vite rescans entries)

Common gotchas

Sidebar/topbar/footer missing. You forgot data-shell="admin" on <body>. Without it, the Vite plugin skips shell injection.

Sidebar renders but no item is highlighted. Your data-page value doesn’t match any key in NAV. Both have to agree exactly — strings are case-sensitive.

Breadcrumbs are blank. Forgot data-breadcrumb, or used the wrong separator. The separator is > with spaces around it: "Home > Section > Page".

Page renders without styles. Make sure <script type="module" src="/src/main-v4.js"></script> is in <head>. That’s the entry that imports main.scss.

Vite says “404 — page not found”. You added the HTML but didn’t restart the dev server. Vite reads the entry list only at startup.