Adding a page and a menu item
Add a page
1. Create the Razor page src/Orbix/Pages/reports/sales.cshtml:
@page
@{
Layout = "_Layout";
ViewData["Title"] = "reports.sales.title";
ViewData["Nav"] = "reports-sales";
}
<ox-page-header title="reports.sales.title" subtitle="reports.sales.subtitle" />
<div class="fd-page">
<section class="fd-card">
<div class="fd-card__body">
<p data-i18n="reports.sales.empty">@L["reports.sales.empty"]</p>
</div>
</section>
</div>L is the IStringLocalizer of the template (see Languages); data-i18n lets the in-page
translator switch the text without a reload.
2. Register the URL in Infrastructure/Navigation/PageRoutes.cs (Razor page => URL):
["/reports/sales"] = "reports/sales",Every page file must be listed (the tests check it). Links name the page, so the URL can change here without
touching them: <a href="@Url.Page("/reports/sales")">.
3. Add the texts to every Locales/<code>.json file (see Languages):
"reports.sales.title": "Sales report",
"reports.sales.subtitle": "Revenue by channel and region",
"reports.sales.empty": "No sales in this period yet."4. Optional page script. Create ClientApp/js/pages/reports.ts and set ViewData["Module"] = "reports":
// ClientApp/js/pages/reports.ts
export function init(): void {
document.querySelectorAll<HTMLElement>('[data-report-refresh]').forEach((button) => {
button.addEventListener('click', () => location.reload());
});
}The module is loaded only on pages that declare it, so each page downloads the code it needs. Run npm run build.
Visit http://localhost:5000/reports/sales.
Pages that need real data add a page model:
@model SalesModelwith aSalesModel : PageModelclass next to the page (OnGetAsync()loads the data from your services), exactly as in any Razor Pages app.
Reusable blocks (partials)
The template's blocks live in Pages/Shared/Partials and are rendered with the <ox-partial> tag helper. Text
arguments use the arg- prefix; markup goes in named <ox-slot> elements or in the body:
<ox-partial name="apps/mail-row" arg-from="Harbor & Tide" arg-subject="Q3 report" arg-preview="Numbers attached."
arg-date="2026-10-18T09:40:00Z" arg-star="false" />Inside the partial, Model["from"] reads an argument (encoded once by Razor), Model.Slot("badge") a named slot and
Model.Body the body. Your own partials can use the standard <partial name="..." model="..." /> as well.
Add a menu item
The sidebar, the horizontal menu, the two-column rail and the command palette are all built from
src/Orbix/menu.json. Sections hold items and groups; groups hold children:
{
"id": "reports",
"key": "nav.reports",
"icon": "lc-chart-column",
"items": [
{ "id": "reports-sales", "key": "nav.sales_report", "icon": "lc-chart-line", "page": "/reports/sales" },
{
"id": "reports-more",
"key": "nav.more_reports",
"icon": "lc-folder-open",
"children": [
{ "id": "reports-stock", "key": "nav.stock_report", "page": "/reports/stock" }
]
}
]
}| Key | Meaning |
|---|---|
id | Unique navigation id |
page | Razor page of the entry; it is highlighted, and its group opens, when that page is rendered |
key | Translation key of the label |
icon | Icon id in the sprite: lc-* (Lucide), tb-* (Tabler), fd-i-* or ox-i-* (Orbix) |
badge | Optional counter: { "count": 4, "tone": "primary" } |
menu.json is read when the app starts: restart it (or let dotnet watch do it) after editing the file.
Icons
Use any icon of the bundled sets with an SVG <use> reference:
<svg class="fd-icon" aria-hidden="true"><use href="#lc-chart-line"/></svg>Browse the sets on the Icons pages of the template. Only the icons you reference are included in the sprite:
after adding a new lc-* or tb-* icon, run npm run assets (it also runs with npm run dev and
npm run build).