All articles
Angular Admin
Angular
NgStarter UI

Angular Admin Layout: Build a Basic Application Shell with NgStarter UI

In this angular admin tutorial, we will build a minimal but production-ready application shell: root layout, side navigation, workspace, header, and scrollable content. You can use this structure as the foundation for an admin panel, CRM, SaaS dashboard, or internal Angular product.

June 12, 20268 min read

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.

src/styles.scss
@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.

src/app/app.config.ts
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.

src/app/app-shell.ts
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.

src/app/app-shell.html
<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.

src/styles.scss
@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.

src/app/app.routes.ts
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-content is scrollable.
  • Primary navigation is composed with ngs-sidebar inside ngs-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.