What belongs in a basic angular admin layout
A basic angular admin layout should be composed from structural UI components instead of ad hoc wrappers. The outer viewport is handled by ngs-layout, the app navigation sits inside ngs-sidenav-container, and the main workspace lives in ngs-panel. This gives the application stable viewport height, clear regions, and scroll behavior only where it belongs.
Step 1. Import the theme
Import a theme once in the global styles.scss. Do not duplicate reset or base application styles: the NgStarter theme already includes the required foundation styles for an angular admin interface.
@use '@ngstarter-ui/components/styles/themes/default';Step 2. Configure the theme provider
Add provideNgsTheme to the application configuration. This is where you set the theme, density, radius, and primary color instead of scattering visual values across individual components.
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideNgsTheme } from '@ngstarter-ui/components/core';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideNgsTheme({
theme: 'modern',
colorScheme: 'auto',
density: 'compact',
radius: 'small',
primaryColor: '#155eef',
}),
],
};Step 3. Create the angular admin shell component
The shell component owns the persistent parts of the angular admin experience: navigation rail, header, workspace, and the outlet for child routes. For new Angular code, use standalone components, ChangeDetectionStrategy.OnPush, and public secondary entry point imports.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { Button } from '@ngstarter-ui/components/button';
import { Icon } from '@ngstarter-ui/components/icon';
import { Layout, LayoutContent } from '@ngstarter-ui/components/layout';
import { Panel, PanelContent, PanelHeader } from '@ngstarter-ui/components/panel';
import { ScrollbarArea } from '@ngstarter-ui/components/scrollbar-area';
import {
Sidebar,
SidebarBody,
SidebarHeader,
SidebarNav,
SidebarNavItem,
SidebarNavItemIconDirective,
} from '@ngstarter-ui/components/sidebar';
import {
Sidenav,
SidenavContainer,
SidenavContent,
} from '@ngstarter-ui/components/sidenav';
import { Toolbar, ToolbarSpacer, ToolbarTitle } from '@ngstarter-ui/components/toolbar';
@Component({
selector: 'app-shell',
imports: [
Button,
Icon,
Layout,
LayoutContent,
Panel,
PanelContent,
PanelHeader,
RouterLink,
RouterLinkActive,
RouterOutlet,
ScrollbarArea,
Sidebar,
SidebarBody,
SidebarHeader,
SidebarNav,
SidebarNavItem,
SidebarNavItemIconDirective,
Sidenav,
SidenavContainer,
SidenavContent,
Toolbar,
ToolbarSpacer,
ToolbarTitle,
],
templateUrl: './app-shell.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppShell {
readonly navItems = [
{ label: 'Dashboard', icon: 'fluent:home-24-regular', href: '/dashboard' },
{ label: 'Customers', icon: 'fluent:people-24-regular', href: '/customers' },
{ label: 'Reports', icon: 'fluent:chart-multiple-24-regular', href: '/reports' },
];
}Step 4. Compose the layout template
The important order is ngs-layout, then ngs-layout-content, then ngs-sidenav-container. Inside the container, place ngs-sidenav and ngs-sidenav-content, with the workspace inside ngs-panel. Page content scrolls through ngs-scrollbar-area.
<ngs-layout root>
<ngs-layout-content>
<ngs-sidenav-container>
<ngs-sidenav>
<ngs-sidebar>
<ngs-sidebar-header>
<a class="brand-link" routerLink="/">
<ngs-icon name="fluent:cube-24-filled" />
<span>Acme Admin</span>
</a>
</ngs-sidebar-header>
<ngs-sidebar-body>
<ngs-sidebar-nav>
@for (item of navItems; track item.href) {
<a
ngs-sidebar-nav-item
[routerLink]="item.href"
routerLinkActive="is-active"
>
<ngs-icon ngsSidebarNavItemIcon [name]="item.icon" />
{{ item.label }}
</a>
}
</ngs-sidebar-nav>
</ngs-sidebar-body>
</ngs-sidebar>
</ngs-sidenav>
<ngs-sidenav-content>
<ngs-panel>
<ngs-panel-header>
<ngs-toolbar>
<ngs-toolbar-title>Dashboard</ngs-toolbar-title>
<ngs-toolbar-spacer />
<button ngsButton="outlined">Invite user</button>
</ngs-toolbar>
</ngs-panel-header>
<ngs-panel-content>
<ngs-scrollbar-area>
<div class="workspace-content">
<router-outlet />
</div>
</ngs-scrollbar-area>
</ngs-panel-content>
</ngs-panel>
</ngs-sidenav-content>
</ngs-sidenav-container>
</ngs-layout-content>
</ngs-layout>Step 5. Add global styles
Keep the shell component focused on structure. Put shared shell styling in the global src/styles.scss file so the application owns these layout rules in one place.
@use '@ngstarter-ui/components/styles/themes/default';
.brand-link {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--ngs-color-on-surface);
font-weight: 650;
text-decoration: none;
ngs-icon {
width: 1.5rem;
height: 1.5rem;
color: var(--ngs-color-primary);
}
}
.workspace-content {
padding: 1.5rem;
}
ngs-panel {
height: 100%;
}Step 6. Connect child routes
Make the shell a parent route. Then every working screen renders inside the router-outlet, while the side navigation and header stay mounted across route changes.
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./app-shell').then((c) => c.AppShell),
children: [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard').then((c) => c.Dashboard),
},
{
path: '',
pathMatch: 'full',
redirectTo: 'dashboard',
},
],
},
];What to check after building
- The root shell fills the viewport and does not create a second document scroll.
- Only the content inside
ngs-panel-contentis scrollable. - Primary navigation is composed with
ngs-sidebarinsidengs-sidenav. - Buttons, icons, cards, forms, and tables are added with NgStarter UI components.
From here, you can add angular admin dashboard widgets, DataView tables, settings forms, notification popovers, and other working screens without changing the base application structure.