Skip to content
Angular ng libraries 4 min read

Angular CDK

The Angular Component Dev Kit (CDK) is a collection of unstyled, behavior-only primitives that power Angular Material — but you can use them on their own to build your own components. Instead of shipping finished widgets, the CDK gives you the hard parts: positioning floating panels, managing focus, virtual scrolling, drag-and-drop, and ARIA wiring. Because it carries no visual opinion, you get robust, accessible behavior while keeping full control over markup and styling. This makes it the foundation for any serious in-house component library.

Installing the CDK

The CDK ships as a standalone package and does not require Angular Material. Add it with the CLI so the correct version is resolved against your Angular release.

ng add @angular/cdk

Each feature lives in its own secondary entry point (@angular/cdk/overlay, @angular/cdk/drag-drop, @angular/cdk/a11y, and so on), and every public API is provided as standalone directives or injectable services. You import only what you use, which keeps bundles small.

Overlays and portals

The Overlay module renders floating content — tooltips, menus, popovers, custom dialogs — in a dedicated layer above your app, with smart positioning that flips and shifts to stay on screen. You combine it with the Portal module, which lets you render a template or component into a different DOM location.

import { Component, inject, TemplateRef, viewChild } from '@angular/core';
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-menu-trigger',
  standalone: true,
  imports: [OverlayModule],
  template: `
    <button (click)="open()" #trigger>Options</button>
    <ng-template #menu>
      <ul class="menu">
        <li>Edit</li>
        <li>Duplicate</li>
        <li>Delete</li>
      </ul>
    </ng-template>
  `,
})
export class MenuTriggerComponent {
  private overlay = inject(Overlay);
  private vcr = inject(ViewContainerRef);

  private trigger = viewChild.required<HTMLButtonElement>('trigger');
  private menu = viewChild.required<TemplateRef<unknown>>('menu');

  open(): void {
    const positionStrategy = this.overlay
      .position()
      .flexibleConnectedTo(this.trigger())
      .withPositions([
        { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
      ]);

    const ref = this.overlay.create({
      positionStrategy,
      scrollStrategy: this.overlay.scrollStrategies.reposition(),
      hasBackdrop: true,
    });

    ref.attach(new TemplatePortal(this.menu(), this.vcr));
    ref.backdropClick().subscribe(() => ref.dispose());
  }
}

Always call ref.dispose() (or detach) when the overlay closes. Overlays attach to the global container, so an undisposed overlay leaks DOM and event listeners that the component lifecycle will not clean up for you.

Drag and drop

The drag-drop module turns ordinary elements into draggable items and reorderable lists with a few directives. The cdkDropList directive defines a sortable container, cdkDrag marks each item, and (cdkDropListDropped) fires with the source and target indices so you can mutate your data model.

import { Component, signal } from '@angular/core';
import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-task-board',
  standalone: true,
  imports: [DragDropModule],
  template: `
    <ul cdkDropList (cdkDropListDropped)="drop($event)">
      @for (task of tasks(); track task) {
        <li cdkDrag>{{ task }}</li>
      }
    </ul>
  `,
})
export class TaskBoardComponent {
  tasks = signal(['Design', 'Build', 'Test', 'Ship']);

  drop(event: CdkDragDrop<string[]>): void {
    this.tasks.update((list) => {
      const next = [...list];
      moveItemInArray(next, event.previousIndex, event.currentIndex);
      return next;
    });
  }
}

For moving items between lists, connect containers with [cdkDropListConnectedTo] and use transferArrayItem instead of moveItemInArray.

Accessibility utilities

The a11y package solves the parts of accessibility that are easy to get wrong. FocusTrap keeps keyboard focus inside an open dialog, LiveAnnouncer pushes messages to screen readers via an ARIA live region, and FocusMonitor tells you whether an element was focused by keyboard, mouse, or programmatically.

import { Component, inject } from '@angular/core';
import { LiveAnnouncer } from '@angular/cdk/a11y';

@Component({
  selector: 'app-save-button',
  standalone: true,
  template: `<button (click)="save()">Save</button>`,
})
export class SaveButtonComponent {
  private announcer = inject(LiveAnnouncer);

  save(): void {
    // ...persist data...
    this.announcer.announce('Document saved', 'polite');
  }
}

Virtual scrolling

The scrolling module renders only the rows visible in the viewport, so a list of tens of thousands of items stays smooth. Wrap repeated content in <cdk-virtual-scroll-viewport> and replace @for with *cdkVirtualFor.

<cdk-virtual-scroll-viewport itemSize="48" class="viewport">
  <div *cdkVirtualFor="let row of rows" class="row">{{ row.name }}</div>
</cdk-virtual-scroll-viewport>

Module reference

Entry pointWhat it provides
@angular/cdk/overlayFloating panel rendering and positioning
@angular/cdk/portalRender templates/components into a dynamic location
@angular/cdk/drag-dropDraggable items and sortable lists
@angular/cdk/a11yFocus trapping, live announcements, focus monitoring
@angular/cdk/scrollingVirtual scrolling and scroll dispatch
@angular/cdk/clipboardCopy text to the clipboard
@angular/cdk/layoutResponsive breakpoint observation

Best practices

  • Import only the secondary entry points you use so unused features are tree-shaken out of the bundle.
  • Dispose every overlay you create — wire backdropClick() or a detachment to ref.dispose() to avoid DOM leaks.
  • Prefer flexibleConnectedTo with multiple fallback positions so floating panels stay on screen at any viewport size.
  • Use LiveAnnouncer and FocusTrap for any custom modal or async action; the CDK gives you correct ARIA behavior for free.
  • Reach for virtual scrolling once a list exceeds a few hundred rows, and set an accurate itemSize for stable scrolling.
  • Treat the CDK as the layer beneath your design system: build styled components on top of its behavior primitives rather than re-implementing positioning and focus logic.
Last updated June 14, 2026
Was this helpful?