Settings sidebar
Vertical tab navigation for settings-style pages where the section list outgrew a horizontal tab strip.
Renders as a 200px left rail at lg+ and a dropdown below that.
Reuses the same TabNavModel and
TabItem records as
horizontal tabs, so a page can swap between layouts without rebuilding its tab data.
Active, inactive, and hover states
The active item uses brand-coloured text on a brand-soft tint so it stands out clearly on the cream page background in light mode.
Hover gives inactive items a gray-100 wash. Active styling is driven by aria-current="page" so client-side
switchTab() can flip the active item by toggling that attribute alone — no partial re-render.
Partner terms
Section content for the active tab.
Mobile dropdown
Below lg, the sidebar collapses to an Alpine.js dropdown. The trigger shows the active tab label;
the panel lists all tabs and dismisses on outside click or escape. Resize this page to mobile to see it.
Usage
Build a TabNavModel with the section list, then drop the partial into a two-column grid alongside the swap target.
The partial handles both the desktop rail and the mobile dropdown.
@{
var settingsTabNav = new TabNavModel
{
TargetId = "settings-tabs",
ActiveTab = Model.ActiveTab,
PushHistory = false,
Breakpoint = "lg",
Tabs =
[
new TabItem { Key = "organisation", Label = "Organisation", Url = orgTabUrl, Skeleton = "orgSettings" },
new TabItem { Key = "discounts", Label = "Discounts", Url = discountsTabUrl, Skeleton = "table" },
new TabItem { Key = "partner-terms", Label = "Partner terms", Url = termsTabUrl, Skeleton = "termsList" },
// ...
]
};
}
<div data-tab-wrapper class="grid grid-cols-1 lg:grid-cols-[200px_1fr] gap-x-8 gap-y-6">
<partial name="_SettingsSidebar" model="settingsTabNav" />
<div id="settings-tabs">
@switch (Model.ActiveTab)
{
case "organisation": <partial name="_Organisation" model="Model" /> break;
case "discounts": <partial name="_Discounts" model="Model" /> break;
case "partner-terms": <partial name="_PartnerTerms" model="Model" /> break;
}
</div>
</div>
Key rules
- Use the settings sidebar when there are 4+ sections — below that, prefer horizontal tabs
- Tab keys map to
?tab=<key>querystring values; the page model selects the active tab from that PushHistory = false— settings tab swaps shouldn't grow the back-button historyBreakpoint = "lg"— must be a literallg, hardcoded in the partial; Tailwind can't see dynamically-interpolated breakpoint classes- The sidebar is
stickybelow the page header so it stays visible when long sections scroll - Active state is driven by
aria-current="page"soswitchTab()updates the active item without re-rendering the partial - Each
TabItemsets aSkeletonstring so the section pre-renders the right shape during AJAX swap (see skeletons)