| |

Angular 91 🅰️ Angular CDK

⏱️ Estimated read: 18 min | |

The Angular CDK — Component Dev Kit — is a set of behavior primitives for building UI components. It is the foundation that Angular Material is built on, but it is not Angular Material. The CDK provides the interaction patterns, the accessibility infrastructure, and the low-level mechanics. It does not provide the visual design. When you use the CDK, you get a set of well-tested tools and you bring your own styles .

This distinction matters. Angular Material gives you a complete design system with buttons, tables, and dialogs that look like Material Design. The CDK gives you the behavior behind those components without the look. A cdk-table is a data table with sorting, pagination, and sticky headers — but no colors, no fonts, no spacing. You style it yourself. This makes the CDK the right tool when you need the behavior of a complex component but the design must match your application’s own visual language.

Key point: The CDK is organized into three categories: common behaviors (accessibility, drag-and-drop, overlay, scrolling), unstyled components (dialog, table, stepper, tree), and utilities (coercion, keycodes, testing) . Every component in Angular Material is built on top of these primitives. When you use the CDK directly, you are using the same foundation the Angular team uses.


Why the CDK exists

Angular Material ships with a complete design system. But not every application wants Material Design. Some have their own design system. Some need a component that Material does not provide. Some need the accessibility behavior of a Material component without the styling overhead.

The design system problem. A company with its own design language — a brand-specific button, a custom table style, a unique dialog layout — cannot use Angular Material’s components without overriding the styles. Overriding is possible but fragile. The CDK provides the behavior without the styles, so the component starts from a blank slate .

The accessibility problem. Building an accessible dialog, menu, or form control from scratch is difficult. The CDK provides FocusTrap, FocusMonitor, LiveAnnouncer, and ListKeyManager — the same tools Angular Material uses internally . A custom component built on these tools has the same accessibility guarantees as a Material component.

The composition problem. A component like a date picker needs an overlay to position the calendar, a list key manager to navigate the dates, a focus trap to keep focus inside the calendar, and a portal to render the calendar outside the component’s DOM hierarchy. The CDK provides each of these as a separate, composable primitive .

The trade-off. The CDK is lower-level than Angular Material. A cdk-table requires you to define the columns, the header rows, the cell templates, and the sorting logic. A mat-table does most of this for you. The CDK requires more code but gives more control. The choice depends on whether the control is needed.


a. Common Behaviors

The CDK’s common behaviors are the utilities that many components share. They solve problems that are not specific to any one component.

Accessibility (@angular/cdk/a11y) provides the tools for building accessible components. FocusMonitor reports how an element received focus — mouse, keyboard, touch, or programmatically . FocusTrap constrains Tab focus within an element, which is required for modal dialogs . LiveAnnouncer announces messages to screen readers using an ARIA live region . ListKeyManager manages keyboard navigation in lists that use role="menu" or role="listbox" .

Overlay (@angular/cdk/overlay) positions floating panels on the screen. It provides two positioning strategies: GlobalPositionStrategy for overlays positioned relative to the viewport (modals, notifications), and ConnectedPositionStrategy for overlays positioned relative to a trigger element (menus, tooltips, dropdowns) . The overlay is what makes mat-select, mat-menu, and mat-dialog work.

Portal (@angular/cdk/portal) renders dynamic content into a different part of the DOM. A Portal is a piece of UI — a component or a template — and a PortalOutlet is the slot where it is rendered. Overlays are built on portals: the overlay creates a PortalOutlet at the document body, and the content is attached as a Portal .

Drag and Drop (@angular/cdk/drag-drop) creates draggable elements and sortable lists. cdkDrag makes any element draggable. cdkDropList makes a list sortable. The package handles the preview, the placeholder, the boundary constraints, and the axis locking .

Scrolling (@angular/cdk/scrolling) provides virtual scrolling for large lists. cdk-virtual-scroll-viewport renders only the items that are visible, simulating the full list by making the container height match the total item count. This allows lists of 100,000 items to render without performance degradation .

Layout (@angular/cdk/layout) provides BreakpointObserver for responding to viewport size changes and media queries. It includes predefined breakpoints like Handset, Tablet, and Web, and allows custom media queries .


b. Unstyled Components

The CDK’s components are the behavior of Angular Material’s components without the styling. They are the same components, stripped of the Material Design tokens.

CdkTable (@angular/cdk/table) is the data table behavior. It provides the column definitions, the row definitions, the cell outlets, and the sticky header/footer support. It does not provide sorting or pagination — those are separate utilities that you integrate .

CdkDialog (@angular/cdk/dialog) is the modal dialog behavior. It provides the overlay, the focus trap, the backdrop, and the data injection. It does not provide the Material dialog’s header, content, and action sections .

CdkStepper (@angular/cdk/stepper) is the step-by-step workflow behavior. It provides the step definitions, the linear/non-linear navigation, the selection change events, and the error state. It does not provide the Material stepper’s visual indicators .

CdkMenu, CdkListbox, CdkTree, CdkAccordion are the other unstyled components. Each provides the behavior of its Material counterpart without the design .


c. Utilities

The CDK’s utilities are the helper functions that the components use internally.

Testing (@angular/cdk/testing) provides component test harnesses. A harness is a class that interacts with a component through its public API, not its DOM structure. This makes tests resilient to internal changes. The harnesses are used by Angular Material’s own tests and are available for custom components .

Coercion (@angular/cdk/coercion) provides functions for converting values to specific types. coerceBooleanProperty, coerceNumberProperty, and coerceStringArray are used by components that accept inputs in multiple formats .

Keycodes (@angular/cdk/keycodes) provides constants for keyboard keys. ENTER, ESCAPE, SPACE, and TAB are the most common. Using the constants instead of raw key codes makes the code more readable .

Collections (@angular/cdk/collections) provides DataSource, an abstract class for connecting data to a component. The connect method returns an observable of the data, and the disconnect method cleans up subscriptions. CdkTable and cdk-virtual-scroll-viewport both accept a DataSource .


Complete Example Session

This session builds a custom dropdown component using the CDK’s overlay, portal, and accessibility utilities.

// ============================================
// PART 1: THE IMPORTS
// ============================================

import { Component, inject, signal, viewChild, ElementRef } from '@angular/core';
import { OverlayModule, Overlay, ConnectedPosition } from '@angular/cdk/overlay';
import { A11yModule, FocusMonitor } from '@angular/cdk/a11y';
import { PortalModule } from '@angular/cdk/portal';

// ============================================
// PART 2: THE DROPDOWN COMPONENT
// ============================================

@Component({
  selector: 'app-custom-dropdown',
  imports: [OverlayModule, A11yModule, PortalModule],
  template: `
    <button
      #trigger
      type="button"
      class="dropdown-trigger"
      (click)="toggle()"
      aria-haspopup="listbox"
      [attr.aria-expanded]="isOpen()"
    >
      {{ selected() }}
      <span class="chevron">▼</span>
    </button>

    <ng-template
      cdkConnectedOverlay
      [cdkConnectedOverlayOrigin]="trigger"
      [cdkConnectedOverlayOpen]="isOpen()"
      [cdkConnectedOverlayPositions]="positions"
      [cdkConnectedOverlayHasBackdrop]="true"
      (backdropClick)="close()"
      (detach)="close()"
    >
      <div class="dropdown-panel" role="listbox">
        @for (option of options; track option) {
          <div
            role="option"
            class="dropdown-option"
            [class.selected]="option === selected()"
            [attr.aria-selected]="option === selected()"
            (click)="select(option)"
          >
            {{ option }}
          </div>
        }
      </div>
    </ng-template>
  `,
  styles: [`
    .dropdown-trigger {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 8px 12px;
      border: 1px solid #ccc;
      border-radius: 4px;
      background: white;
      cursor: pointer;
      min-width: 150px;
      justify-content: space-between;
    }
    .dropdown-panel {
      background: white;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.15);
      min-width: 150px;
      padding: 4px 0;
    }
    .dropdown-option {
      padding: 8px 12px;
      cursor: pointer;
    }
    .dropdown-option:hover {
      background: #f0f0f0;
    }
    .dropdown-option.selected {
      background: #e3f2fd;
      font-weight: 500;
    }
  `],
})
export class CustomDropdownComponent {
  private focusMonitor = inject(FocusMonitor);

  options = ['Option 1', 'Option 2', 'Option 3'];
  isOpen = signal(false);
  selected = signal('Select an option');

  trigger = viewChild.required<ElementRef>('trigger');

  // Position the panel below the trigger, or above if there is no room
  positions: ConnectedPosition[] = [
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'top',
      offsetY: 4,
    },
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetY: -4,
    },
  ];

  toggle(): void {
    this.isOpen.update((v) => !v);
  }

  close(): void {
    this.isOpen.set(false);
    // Restore focus to the trigger
    this.trigger().nativeElement.focus();
  }

  select(option: string): void {
    this.selected.set(option);
    this.close();
  }
}

The cdkConnectedOverlay directive creates the overlay and positions it relative to the trigger. The positions array defines the preferred positions. The cdkConnectedOverlayHasBackdrop option adds a backdrop that closes the dropdown when clicked. The cdkConnectedOverlayOpen binding controls visibility. The (detach) event fires when the overlay is closed by the user pressing Escape or clicking outside .

// ============================================
// PART 3: THE CUSTOM TABLE
// ============================================

import { Component, signal } from '@angular/core';
import { CdkTableModule } from '@angular/cdk/table';
import { CdkScrollableModule } from '@angular/cdk/scrolling';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

@Component({
  selector: 'app-custom-table',
  imports: [CdkTableModule, CdkScrollableModule],
  template: `
    <table cdk-table [dataSource]="users()" class="custom-table">
      <ng-container cdkColumnDef="id">
        <th cdk-header-cell *cdkHeaderCellDef>ID</th>
        <td cdk-cell *cdkCellDef="let user">{{ user.id }}</td>
      </ng-container>

      <ng-container cdkColumnDef="name">
        <th cdk-header-cell *cdkHeaderCellDef>Name</th>
        <td cdk-cell *cdkCellDef="let user">{{ user.name }}</td>
      </ng-container>

      <ng-container cdkColumnDef="email">
        <th cdk-header-cell *cdkHeaderCellDef>Email</th>
        <td cdk-cell *cdkCellDef="let user">{{ user.email }}</td>
      </ng-container>

      <ng-container cdkColumnDef="role">
        <th cdk-header-cell *cdkHeaderCellDef>Role</th>
        <td cdk-cell *cdkCellDef="let user">{{ user.role }}</td>
      </ng-container>

      <tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
      <tr cdk-row *cdkRowDef="let row; columns: displayedColumns"></tr>
    </table>
  `,
  styles: [`
    .custom-table {
      width: 100%;
      border-collapse: collapse;
    }
    .custom-table th {
      text-align: left;
      padding: 12px 16px;
      border-bottom: 2px solid #e0e0e0;
      font-weight: 600;
    }
    .custom-table td {
      padding: 12px 16px;
      border-bottom: 1px solid #f0f0f0;
    }
    .custom-table tr:hover td {
      background: #fafafa;
    }
  `],
})
export class CustomTableComponent {
  displayedColumns = ['id', 'name', 'email', 'role'];

  users = signal<User[]>([
    { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
    { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
    { id: 3, name: 'Carol Williams', email: 'carol@example.com', role: 'Editor' },
  ]);
}

The cdk-table provides the data table behavior: the column definitions, the row definitions, and the cell outlets. The cdkColumnDef defines each column. The cdkHeaderCellDef and cdkCellDef define the header and cell templates. The cdkHeaderRowDef and cdkRowDef define the rows .

// ============================================
// PART 4: THE VIRTUAL SCROLL LIST
// ============================================

import { Component } from '@angular/core';
import { ScrollingModule } from '@angular/cdk/scrolling';

@Component({
  selector: 'app-virtual-list',
  imports: [ScrollingModule],
  template: `
    <cdk-virtual-scroll-viewport itemSize="50" class="viewport">
      <div *cdkVirtualFor="let item of items" class="item">
        {{ item }}
      </div>
    </cdk-virtual-scroll-viewport>
  `,
  styles: [`
    .viewport {
      height: 300px;
      width: 100%;
      border: 1px solid #ccc;
    }
    .item {
      height: 50px;
      display: flex;
      align-items: center;
      padding: 0 16px;
      border-bottom: 1px solid #f0f0f0;
    }
  `],
})
export class VirtualListComponent {
  items = Array.from({ length: 100000 }, (_, i) => `Item #${i}`);
}

The cdk-virtual-scroll-viewport renders only the visible items. The itemSize property must match the actual height of each item. The *cdkVirtualFor replaces *ngFor inside the viewport .

// ============================================
// PART 5: THE RESPONSIVE LAYOUT
// ============================================

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-responsive-layout',
  template: `
    <div class="layout" [class.mobile]="isMobile()">
      @if (!isMobile()) {
        <aside class="sidebar">
          <p>Sidebar</p>
        </aside>
      }
      <main class="content">
        <p>Main content</p>
      </main>
    </div>
  `,
  styles: [`
    .layout {
      display: flex;
      height: 100vh;
    }
    .layout.mobile {
      flex-direction: column;
    }
    .sidebar {
      width: 250px;
      background: #f5f5f5;
      padding: 16px;
    }
    .content {
      flex: 1;
      padding: 16px;
    }
  `],
})
export class ResponsiveLayoutComponent {
  private breakpointObserver = inject(BreakpointObserver);

  isMobile = toSignal(
    this.breakpointObserver
      .observe([Breakpoints.Handset])
      .pipe(map((result) => result.matches)),
    { initialValue: false }
  );
}

The BreakpointObserver observes the Handset breakpoint, which matches mobile devices. The toSignal function converts the observable to a signal that the template can read .

// ============================================
// PART 6: THE DRAG AND DROP LIST
// ============================================

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

@Component({
  selector: 'app-drag-list',
  imports: [DragDropModule],
  template: `
    <div cdkDropList class="list" (cdkDropListDropped)="drop($event)">
      @for (item of items(); track item) {
        <div cdkDrag class="item">
          {{ item }}
          <div *cdkDragPlaceholder class="placeholder"></div>
        </div>
      }
    </div>
  `,
  styles: [`
    .list {
      width: 300px;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    .item {
      padding: 12px 16px;
      border-bottom: 1px solid #f0f0f0;
      background: white;
      cursor: grab;
    }
    .item:active {
      cursor: grabbing;
    }
    .placeholder {
      background: #e3f2fd;
      height: 48px;
    }
  `],
})
export class DragListComponent {
  items = signal(['Item 1', 'Item 2', 'Item 3', 'Item 4']);

  drop(event: CdkDragDrop<string[]>): void {
    const current = this.items();
    const updated = [...current];
    moveItemInArray(updated, event.previousIndex, event.currentIndex);
    this.items.set(updated);
  }
}

The cdkDropList directive creates a sortable list. The cdkDrag directive makes each item draggable. The cdkDropListDropped event fires when an item is dropped, and moveItemInArray reorders the array .

// ============================================
// PART 7: THE CLIPBOARD DIRECTIVE
// ============================================

import { Component } from '@angular/core';
import { ClipboardModule } from '@angular/cdk/clipboard';

@Component({
  selector: 'app-copy-button',
  imports: [ClipboardModule],
  template: `
    <button [cdkCopyToClipboard]="textToCopy" (cdkCopyToClipboardCopied)="onCopied($event)">
      Copy to Clipboard
    </button>
    @if (copied) {
      <span>Copied!</span>
    }
  `,
})
export class CopyButtonComponent {
  textToCopy = 'Hello from the CDK clipboard!';
  copied = false;

  onCopied(success: boolean): void {
    this.copied = success;
    setTimeout(() => (this.copied = false), 2000);
  }
}

The cdkCopyToClipboard directive copies the bound text to the system clipboard. The cdkCopyToClipboardCopied event fires with a boolean indicating success .

// ============================================
// PART 8: THE TEST HARNESS
// ============================================

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { CustomDropdownComponent } from './custom-dropdown.component';

describe('CustomDropdownComponent', () => {
  let fixture: ComponentFixture<CustomDropdownComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CustomDropdownComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(CustomDropdownComponent);
    loader = TestbedHarnessEnvironment.loader(fixture);
    fixture.detectChanges();
  });

  it('should open the dropdown', () => {
    const trigger = fixture.nativeElement.querySelector('button');
    trigger.click();
    fixture.detectChanges();
    expect(fixture.componentInstance.isOpen()).toBe(true);
  });
});

The TestbedHarnessEnvironment provides a loader for test harnesses. The harness interacts with the component through its public API .

// ============================================
// PART 9: THE MODULE IMPORTS
// ============================================

// The CDK is imported in pieces. Each package has its own module.
import { OverlayModule } from '@angular/cdk/overlay';
import { A11yModule } from '@angular/cdk/a11y';
import { PortalModule } from '@angular/cdk/portal';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { LayoutModule } from '@angular/cdk/layout';
import { CdkTableModule } from '@angular/cdk/table';
import { DialogModule } from '@angular/cdk/dialog';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { CdkStepperModule } from '@angular/cdk/stepper';

// Import only what you use. The CDK is tree-shakeable.

Each CDK package is imported separately. The build only includes the code for the imported modules. Unused packages are removed by the bundler .

// ============================================
// PART 10: THE DECISION MATRIX
// ============================================

// Use Angular Material when:
// - The component exists in Material
// - The Material Design look is acceptable
// - You want the Angular team to maintain the UI

// Use the CDK when:
// - The component does not exist in Material
// - The visual design must be custom
// - You need the behavior without the styling
// - You are building a design system

// Use a custom component from scratch when:
// - The interaction pattern is application-specific
// - The CDK primitives are insufficient
// - The component is simple enough to build directly

The decision is about control and reuse. Angular Material provides the most reuse with the least control. The CDK provides the behavior with full control over the design. A custom component provides the most control with the most code to maintain.


Quick Reference

The CDK Categories

CategoryPackagePurpose
Accessibility@angular/cdk/a11yFocus, announcements, keyboard nav
Overlay@angular/cdk/overlayFloating panels, popovers
Portal@angular/cdk/portalDynamic content rendering
Drag and Drop@angular/cdk/drag-dropSortable lists, draggable elements
Scrolling@angular/cdk/scrollingVirtual scrolling, scroll detection
Layout@angular/cdk/layoutBreakpoint detection
Table@angular/cdk/tableData table behavior
Dialog@angular/cdk/dialogModal dialog behavior
Clipboard@angular/cdk/clipboardCopy to clipboard
Stepper@angular/cdk/stepperStep-by-step workflows

The A11y Utilities

UtilityPurpose
FocusMonitorDetect focus origin (mouse, keyboard, touch)
FocusTrapConstrain Tab focus within an element
LiveAnnouncerAnnounce messages to screen readers
ListKeyManagerKeyboard navigation in lists
InteractivityCheckerCheck focusable, visible, enabled states

The Overlay Position Strategies

StrategyPurpose
GlobalPositionStrategyPosition relative to viewport
ConnectedPositionStrategyPosition relative to a trigger
FlexibleConnectedPositionStrategyAdvanced positioning with fallbacks

The Virtual Scroll Properties

PropertyPurpose
itemSizeHeight of each item in pixels
minBufferPxMinimum buffer before rendering more
maxBufferPxMaximum buffer to render
orientationvertical or horizontal

The Drag and Drop Directives

DirectivePurpose
cdkDragMake an element draggable
cdkDropListMake a list sortable
cdkDragHandleRestrict dragging to a handle
cdkDragPreviewCustom drag preview
cdkDragPlaceholderCustom placeholder

Best Practices

✅ Do This:

// Import only the CDK packages you use
import { OverlayModule } from '@angular/cdk/overlay';            // ✅
// Use FocusMonitor to detect keyboard focus
this.focusMonitor.monitor(elementRef).subscribe(origin => { });  // ✅
// Use LiveAnnouncer for dynamic messages
this.liveAnnouncer.announce('Saved', 'polite');                  // ✅
// Use virtual scrolling for large lists
<cdk-virtual-scroll-viewport itemSize="50">                      // ✅
// Use BreakpointObserver for responsive layouts
this.breakpointObserver.observe([Breakpoints.Handset]);          // ✅

❌ Don’t Do This:

// Don't import the entire CDK
import { } from '@angular/cdk';  // no barrel import                // ❌
// Don't use cdkDrag without a drop list for sorting
<div cdkDrag>  // free drag only                                  // ❌
// Don't forget the overlay prebuilt styles
@import '@angular/cdk/overlay-prebuilt.css';                       // ❌ required
// Don't skip the test harness for complex components
// The harness makes tests resilient to DOM changes.              // ❌

Common Pitfalls

PitfallWhy It HappensFix
Overlay not positionedMissing overlay prebuilt stylesImport overlay-prebuilt.css
Virtual scroll items misalignedWrong itemSizeMatch itemSize to actual height
Drag and drop not workingMissing cdkDropListWrap items in a drop list
Focus trap not workingMissing cdkFocusInitialAdd the attribute to the initial focus element
Breakpoint not detectingWrong breakpoint constantCheck the Breakpoints values
Table not renderingMissing column definitionsAdd cdkColumnDef for each column

Real-World Examples

1. Overlay

const overlayRef = overlay.create();
overlayRef.attach(new ComponentPortal(MyComponent));

2. Portal

<ng-template cdkPortal>Content</ng-template>

3. Focus Trap

<div cdkTrapFocus [cdkTrapFocusAutoCapture]="true">...</div>

4. Live Announcer

liveAnnouncer.announce('Changes saved', 'polite');

5. Virtual Scroll

<cdk-virtual-scroll-viewport itemSize="50">...</cdk-virtual-scroll-viewport>

6. Drag and Drop

<div cdkDropList (cdkDropListDropped)="drop($event)">...</div>

7. Breakpoint Observer

breakpointObserver.observe([Breakpoints.Handset]).subscribe(...);

8. CDK Table

<table cdk-table [dataSource]="data">...</table>

9. Clipboard

<button [cdkCopyToClipboard]="text">Copy</button>

10. Dialog

const dialogRef = dialog.open(MyDialogComponent);

Visual

The CDK Categories

┌──────────────────────────────────────────────┐
│  COMMON BEHAVIORS                            │
│    a11y, bidi, clipboard, drag-drop, layout, │
│    observers, overlay, platform, portal,      │
│    scrolling, text-field                      │
│                                              │
│  UNSTYLED COMPONENTS                         │
│    accordion, dialog, listbox, menu, stepper, │
│    table, tree                                │
│                                              │
│  UTILITIES                                   │
│    coercion, collections, keycodes, testing   │
│                                              │
└──────────────────────────────────────────────┘

The Overlay Architecture

┌──────────────────────────────────────────────┐
│  OVERLAY                                     │
│    OverlayRef                                │
│      ├─ PositionStrategy                     │
│      │    ├─ GlobalPositionStrategy          │
│      │    └─ ConnectedPositionStrategy       │
│      ├─ ScrollStrategy                       │
│      │    ├─ NoopScrollStrategy              │
│      │    ├─ CloseScrollStrategy             │
│      │    ├─ BlockScrollStrategy             │
│      │    └─ RepositionScrollStrategy        │
│      └─ PortalOutlet                         │
│           └─ ComponentPortal / TemplatePortal│
│                                              │
└──────────────────────────────────────────────┘

The Virtual Scroll

┌──────────────────────────────────────────────┐
│  VIRTUAL SCROLL                              │
│                                              │
│  Total items: 100,000                        │
│  Visible items: ~10                          │
│  Rendered items: ~20 (with buffer)           │
│                                              │
│  The viewport height matches the total       │
│  item count. Only the visible items are      │
│  rendered. Scrolling shifts which items      │
│  are rendered.                               │
│                                              │
└──────────────────────────────────────────────┘

The Responsive Breakpoints

┌──────────────────────────────────────────────┐
│  BREAKPOINTS                                 │
│                                              │
│  Handset:  (max-width: 599.98px) portrait    │
│            (max-width: 959.98px) landscape   │
│                                              │
│  Tablet:   (min-width: 600px) portrait       │
│            (min-width: 960px) landscape      │
│                                              │
│  Web:      (min-width: 1024px)               │
│                                              │
│  Use BreakpointObserver to react to changes. │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
CDK definitionComponent Dev Kit — behavior primitives
Relationship to MaterialFoundation; Material adds styling
CategoriesCommon behaviors, unstyled components, utilities
AccessibilityFocusMonitor, FocusTrap, LiveAnnouncer, ListKeyManager
OverlayFloating panels, position strategies
PortalDynamic content rendering
Drag and DropSortable lists, draggable elements
ScrollingVirtual scrolling for large lists
LayoutBreakpoint detection
TableData table behavior
TestingComponent test harnesses
Installationnpm install @angular/cdk

Key takeaways:

  • The CDK is a set of behavior primitives, not a design system. It provides the interaction patterns, accessibility infrastructure, and low-level mechanics that Angular Material is built on. It does not provide the visual design .
  • The CDK is organized into three categories: common behaviors, unstyled components, and utilities. The common behaviors include accessibility, overlay, portal, drag-and-drop, scrolling, and layout. The unstyled components include dialog, table, stepper, and tree. The utilities include coercion, keycodes, collections, and testing .
  • The accessibility package is the foundation for building accessible components. FocusMonitor detects how an element received focus. FocusTrap constrains Tab focus in modals. LiveAnnouncer announces messages to screen readers. ListKeyManager handles keyboard navigation .
  • The overlay package positions floating panels. It provides global positioning for modals and connected positioning for menus, tooltips, and dropdowns. The overlay is built on portals, which render content into a different part of the DOM .
  • Virtual scrolling renders only visible items. cdk-virtual-scroll-viewport allows lists of 100,000 items to render without performance degradation. The itemSize property must match the actual height of each item .
  • The CDK is tree-shakeable. Import only the packages you use. The build includes only the code for the imported modules. Unused packages are removed by the bundler.
  • The CDK is the same foundation Angular Material uses. When you use the CDK directly, you are using the same tools the Angular team uses to build the Material components. The difference is that you provide the styling.

Remember: The Angular CDK is the behavior layer. Angular Material is the design layer. The CDK gives you the overlay, the focus trap, the virtual scroll, the drag-and-drop, and the accessibility tools. It does not give you the colors, the fonts, or the spacing. When you need the behavior but not the design, the CDK is the tool. When you need both, Angular Material is the tool. The choice is about control and reuse. The CDK provides more control with more code. Material provides more reuse with less control.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!