Styling and UI Libraries
|
This section documents modern, standalone Angular — signals, the built-in This content was generated with the assistance of AI and should be verified against angular.dev before being relied on in production. Angular ships a major release roughly every six months and its APIs continue to evolve: the examples here target the current major release; where a consulted source disagrees with the current documentation, the documentation wins and the difference is noted. This section’s bibliography lists the reference material consulted while preparing these pages. |
Angular scopes styles to a component by default, so most of an app’s CSS lives next to the component it styles. This page covers the built-in styling options and then surveys the component libraries — free and open-source first, commercial suites last.
Styling options
Component styles and view encapsulation
Styles listed in styles (inline) or styleUrl / styleUrls (files) are scoped to the component.
ViewEncapsulation controls how: Emulated (default) rewrites selectors and adds per-component attributes so
rules do not leak in or out; None adds the rules to the document globally; ShadowDom renders into a real
shadow root. Inside those styles, :host targets the component’s own element, :host-context(.selector) styles
it based on an ancestor, and ::ng-deep forces a rule through to child components — ::ng-deep is
deprecated, so prefer a CSS custom property or a global rule instead.
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-badge',
template: `<span class="dot"></span><ng-content />`,
styles: `
:host { display: inline-block; }
:host(.danger) .dot { background: red; }
:host-context(.dark-theme) { color: #eee; }
`,
encapsulation: ViewEncapsulation.Emulated,
})
export class Badge {}
App-wide rules go in the global stylesheet listed under styles in angular.json (src/styles.css or
src/styles.scss by default). See styling components.
Sass
Scaffold a workspace with Sass by passing --style=scss to ng new (or set it as a schematic default); the
CLI then compiles .scss component and global stylesheets with no extra configuration.
ng new my-app --style=scss
// badge.component.scss
$brand: #1976d2;
:host {
display: inline-block;
.dot { background: $brand; }
}
See the Sass Reference for the language itself.
Tailwind CSS
Install Tailwind CSS per its
Angular framework guide, then use utility
classes directly in templates. Bind them conditionally with [class] (an object or string), [ngClass], or
[class.name].
npm install tailwindcss @tailwindcss/postcss postcss
# add the @tailwindcss/postcss plugin to .postcssrc.json, then
# @import "tailwindcss"; in src/styles.css
<button
class="rounded px-4 py-2 text-white"
[class]="{ 'bg-blue-600 hover:bg-blue-700': !busy(), 'bg-gray-400': busy() }">
Save
</button>
CSS-in-JS and dynamic theming
CSS-in-JS libraries are uncommon in Angular — scoped component styles already give the colocation and
isolation they provide elsewhere. For values that change at runtime, set a CSS custom property with
[style.--name] and consume it from the component stylesheet.
<div class="chart" [style.--bar-color]="color()" [style.--bar-height.px]="height()"></div>
.chart { background: var(--bar-color); height: var(--bar-height); }
NgClass / NgStyle vs. [class.x] / [style.x]
NgClass and NgStyle accept an object, array, or string and were the pre-v16 way to toggle several classes or
styles at once. The native bindings [class.name], [class], [style.prop], and [style] now cover the same
ground, are faster, and need no import — prefer them; reach for NgClass / NgStyle only for a dynamic map
whose keys are not known ahead of time.
<!-- preferred -->
<p [class.active]="isActive()" [style.color]="isActive() ? 'green' : 'gray'">Status</p>
<!-- NgClass / NgStyle: import { NgClass, NgStyle } from '@angular/common' -->
<p [ngClass]="classMap()" [ngStyle]="styleMap()">Status</p>
See the NgClass / NgStyle notes in the directives guide.
Component libraries — free and open-source
Each ships ready-made, accessible components so you are not rebuilding dialogs, menus, and data tables by hand. They are listed roughly in order of adoption.
Angular Material (MIT)
Angular Material is the first-party Material Design library, built on the Angular
CDK and by far the most widely used. Install it with ng add, which also wires up Material 3 (mat.theme)
theming and typography.
ng add @angular/material
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-demo',
imports: [MatButtonModule],
template: `<button mat-flat-button (click)="save()">Save</button>`,
})
export class Demo {
save(): void {}
}
This is a pointer only — see Angular Material for the full treatment of theming and components.
PrimeNG (MIT)
PrimeNG is a large free suite of roughly 90 components with design-token theming
configured through providePrimeNG(). Only the optional templates (starter admin layouts) are paid; every
component is free.
import { ApplicationConfig } from '@angular/core';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';
export const appConfig: ApplicationConfig = {
providers: [providePrimeNG({ theme: { preset: Aura } })],
};
import { Component } from '@angular/core';
import { ButtonModule } from 'primeng/button';
@Component({
selector: 'app-demo',
imports: [ButtonModule],
template: `<p-button label="Save" (onClick)="save()" />`,
})
export class Demo {
save(): void {}
}
ng-bootstrap (MIT) and ngx-bootstrap (MIT)
ng-bootstrap and ngx-bootstrap both re-implement Bootstrap 5 widgets as native Angular components with no jQuery and no Bootstrap JavaScript. Add the Bootstrap CSS, then use the components.
ng add @ng-bootstrap/ng-bootstrap
import { Component, inject } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-demo',
template: `
<button class="btn btn-primary" (click)="open(tpl)">Open</button>
<ng-template #tpl let-modal>
<div class="modal-body">Confirm?</div>
<button class="btn btn-secondary" (click)="modal.close()">Close</button>
</ng-template>
`,
})
export class Demo {
private readonly modalService = inject(NgbModal);
open(content: unknown): void {
this.modalService.open(content);
}
}
See the Bootstrap Reference for the underlying design system.
NG-ZORRO (MIT)
NG-ZORRO is an Ant Design port with a rich enterprise component set — notably
nz-table and nz-form.
ng add ng-zorro-antd
import { Component } from '@angular/core';
import { NzButtonModule } from 'ng-zorro-antd/button';
@Component({
selector: 'app-demo',
imports: [NzButtonModule],
template: `<button nz-button nzType="primary" (click)="save()">Save</button>`,
})
export class Demo {
save(): void {}
}
Taiga UI (Apache-2.0)
Taiga UI is a large, modular, Angular-native kit split into @taiga-ui/core, /kit,
/addon-* packages so you pull in only what you use.
ng add @taiga-ui/cdk @taiga-ui/core @taiga-ui/kit
import { Component } from '@angular/core';
import { TuiButton } from '@taiga-ui/core';
@Component({
selector: 'app-demo',
imports: [TuiButton],
template: `<button tuiButton type="button" (click)="save()">Save</button>`,
})
export class Demo {
save(): void {}
}
spartan/ui (MIT)
spartan/ui ships unstyled, accessible primitives (the brain packages) plus copy-in,
Tailwind-styled components (helm) that you own and edit — the Angular analogue of Radix Primitives plus
shadcn/ui.
npx nx g @spartan-ng/cli:ui button
import { Component } from '@angular/core';
import { HlmButtonDirective } from '@spartan-ng/ui-button-helm';
@Component({
selector: 'app-demo',
imports: [HlmButtonDirective],
template: `<button hlmBtn (click)="save()">Save</button>`,
})
export class Demo {
save(): void {}
}
Clarity (MIT) and Nebular (MIT)
Clarity is VMware’s design system with Angular components and a data grid. Nebular is a theming-focused kit with an auth module. Both are less actively maintained than the libraries above — check recent release activity before adopting.
import { Component } from '@angular/core';
import { ClrButtonModule } from '@clr/angular';
@Component({
selector: 'app-demo',
imports: [ClrButtonModule],
template: `<button class="btn btn-primary" (click)="save()">Save</button>`,
})
export class Demo {
save(): void {}
}
The Angular CDK alone (MIT)
The Angular CDK provides the unstyled behaviour behind a design
system — Overlay, the a11y package, drag-and-drop, virtual scroll, BreakpointObserver, Portal, and
more. Use it directly to build bespoke components without adopting a full visual library.
ng add @angular/cdk
import { Component, inject } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
@Component({
selector: 'app-demo',
template: `@if (handset()) { <p>Compact layout</p> }`,
})
export class Demo {
private readonly bp = inject(BreakpointObserver);
readonly handset = toSignal(
this.bp.observe(Breakpoints.Handset).pipe(map((r) => r.matches)),
);
}
Commercial suites — paid / licensed
These are licensed products; they are listed last and without examples.
-
Kendo UI for Angular — a broad commercial component and data-grid suite from Progress Telerik.
-
Ignite UI for Angular — Infragistics' commercial suite, focused on high-performance grids and charts.
-
Syncfusion — a very large commercial suite; a free community licence is available for individuals and small teams under a revenue threshold.
-
AG Grid — the community edition is MIT-licensed; the enterprise grid features (row grouping, pivoting, server-side row model, and more) are paid.
Accessibility
A component library gives you accessible primitives, but you still own the wiring: label every control, manage
focus in dialogs and menus, honour prefers-reduced-motion, and test with a keyboard and a screen reader. The
CDK a11y package helps — LiveAnnouncer for polite/assertive announcements, cdkTrapFocus to keep focus
inside an open dialog, and FocusMonitor to track how an element gained focus.
import { Component, inject } from '@angular/core';
import { A11yModule, LiveAnnouncer } from '@angular/cdk/a11y';
@Component({
selector: 'app-dialog',
imports: [A11yModule],
template: `
<div cdkTrapFocus role="dialog" aria-label="Confirm delete">
<button (click)="confirm()">Delete</button>
</div>
`,
})
export class Dialog {
private readonly announcer = inject(LiveAnnouncer);
confirm(): void {
this.announcer.announce('Item deleted', 'assertive');
}
}
See Web Accessibility for conformance levels and validation tools.