Skip to content
Angular ng libraries 4 min read

ng-bootstrap

ng-bootstrap is a set of native Angular components built on top of the Bootstrap CSS framework. It re-implements Bootstrap’s interactive widgets — modals, tooltips, date pickers, accordions, and more — as pure Angular components, so you get Bootstrap’s familiar look and feel without pulling in jQuery or Bootstrap’s own JavaScript bundle. If your team already knows Bootstrap markup and wants idiomatic, change-detection-friendly Angular widgets, ng-bootstrap is the natural bridge.

Why ng-bootstrap instead of plain Bootstrap

Vanilla Bootstrap ships interactive behavior in bootstrap.bundle.js, which manipulates the DOM directly and historically depended on jQuery. That fights Angular’s rendering model and breaks change detection. ng-bootstrap solves this by providing the behavior as Angular components and directives while leaving the styling to the official Bootstrap CSS.

ConcernPlain Bootstrap JSng-bootstrap
jQuery dependencyLegacy versions onlyNone
DOM manipulationImperative, outside AngularInside Angular’s render tree
TemplatingHTML strings / data-attributesAngular templates with bindings
Change detectionManual / fragileFirst-class
Tree-shakingWhole bundlePer-component imports

ng-bootstrap supplies only the widgets and their behavior. You still install and import the Bootstrap CSS yourself — the library deliberately does not bundle styles so you control the exact Bootstrap version and theme.

Installation

Install the library and Bootstrap CSS, then wire the stylesheet into your build.

ng add @ng-bootstrap/ng-bootstrap

The schematic adds @ng-bootstrap/ng-bootstrap, installs bootstrap, and registers bootstrap.css in angular.json. If you prefer to do it by hand:

npm install @ng-bootstrap/ng-bootstrap bootstrap

Then add the CSS to the styles array in angular.json:

"styles": [
  "node_modules/bootstrap/dist/css/bootstrap.min.css",
  "src/styles.css"
]

Standalone components and imports

ng-bootstrap exports each widget as a standalone directive or component, so in a modern Angular app you import exactly what a component uses — no NgbModule. Import them directly into a standalone component’s imports array.

import { Component, signal } from '@angular/core';
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-faq',
  standalone: true,
  imports: [NgbAccordionModule],
  templateUrl: './faq.component.html',
})
export class FaqComponent {
  panels = signal([
    { title: 'Shipping', body: 'We ship worldwide within 3 days.' },
    { title: 'Returns', body: '30-day no-questions-asked returns.' },
  ]);
}
<div ngbAccordion>
  @for (panel of panels(); track panel.title) {
    <div ngbAccordionItem>
      <h2 ngbAccordionHeader>
        <button ngbAccordionButton>{{ panel.title }}</button>
      </h2>
      <div ngbAccordionCollapse>
        <div ngbAccordionBody>
          <ng-template>{{ panel.body }}</ng-template>
        </div>
      </div>
    </div>
  }
</div>

Notice the @for control flow and signals driving the panel list — ng-bootstrap markup is just Angular templates, so all the modern features work unchanged.

Modals

Modals are opened imperatively through the injectable NgbModal service. Open returns an NgbModalRef whose result is a promise that resolves when the modal is closed and rejects when dismissed.

import { Component, inject, TemplateRef } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-confirm',
  standalone: true,
  templateUrl: './confirm.component.html',
})
export class ConfirmComponent {
  private modal = inject(NgbModal);

  open(content: TemplateRef<unknown>): void {
    this.modal.open(content, { centered: true }).result.then(
      (reason) => console.log('Closed:', reason),
      () => console.log('Dismissed'),
    );
  }
}
<button class="btn btn-primary" (click)="open(dialog)">Delete account</button>

<ng-template #dialog let-modal>
  <div class="modal-header">
    <h5 class="modal-title">Confirm deletion</h5>
    <button class="btn-close" (click)="modal.dismiss()"></button>
  </div>
  <div class="modal-body">This action cannot be undone.</div>
  <div class="modal-footer">
    <button class="btn btn-secondary" (click)="modal.dismiss()">Cancel</button>
    <button class="btn btn-danger" (click)="modal.close('deleted')">Delete</button>
  </div>
</ng-template>

Output:

Closed: deleted

Tooltips and popovers

Tooltips and popovers are directives — no service, no manual teardown. They position themselves and clean up automatically when the host element is destroyed.

<button class="btn btn-outline-secondary"
        ngbTooltip="Saved 2 minutes ago"
        placement="top">
  Save
</button>

<button class="btn btn-info"
        ngbPopover="Edit access lets users change content."
        popoverTitle="Permissions"
        triggers="mouseenter:mouseleave">
  Editor
</button>

Date picker with reactive forms

The date picker integrates with Angular forms through NgbDateStruct, a plain { year, month, day } object rather than a native Date.

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbDatepickerModule, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-booking',
  standalone: true,
  imports: [FormsModule, NgbDatepickerModule],
  template: `
    <input class="form-control" placeholder="yyyy-mm-dd"
           [(ngModel)]="date" ngbDatepicker #d="ngbDatepicker"
           (click)="d.toggle()" />
  `,
})
export class BookingComponent {
  date: NgbDateStruct | null = null;
}

Global configuration

Each widget ships a config service you can override once, via DI, to set app-wide defaults — handy for consistent tooltip placement or modal sizing.

import { ApplicationConfig } from '@angular/core';
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: NgbTooltipConfig,
      useFactory: () => {
        const config = new NgbTooltipConfig();
        config.placement = 'right';
        config.container = 'body';
        return config;
      },
    },
  ],
};

Best Practices

  • Import Bootstrap’s CSS yourself and keep its version aligned with the ng-bootstrap version you install — check the compatibility table in the release notes before upgrading.
  • Import per-widget modules (e.g. NgbAccordionModule) into standalone components instead of the umbrella NgbModule, so unused widgets tree-shake away.
  • Open modals through the NgbModal service and always handle both branches of result — resolve for close, reject for dismiss — to avoid unhandled promise rejections.
  • Use the per-widget config services to set defaults once rather than repeating placement/container attributes across templates.
  • Pass an injector to NgbModal.open when the modal content needs your component’s DI context.
  • Set container="body" on tooltips and popovers inside scrollable or overflow:hidden containers to prevent clipping.
Last updated June 14, 2026
Was this helpful?