Styling
Every theme exposes its colours, sizes and shadows as CSS custom properties, so the recommended way to restyle ng-select is to set a few --ng-select-* variables. No ::ng-deep, no specificity battles, and because custom properties resolve at runtime you can switch palettes without recompiling.
Theming with CSS variables
Section titled “Theming with CSS variables”Import a theme as usual, then override the variables you care about:
@import '@ng-select/ng-select/themes/default.theme.css';
:root { --ng-select-highlight: #7aa2f7; --ng-select-border: #3a4152; --ng-select-bg: #1f2430;}import { ChangeDetectionStrategy, Component, OnDestroy, signal } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgSelectComponent } from '@ng-select/ng-select';
/** * The dropdown panel renders in the CDK overlay attached to `<body>`, outside this * component's DOM subtree. Custom properties therefore have to be declared on an * ancestor of both — `<body>` here — which is also how a real dark-mode toggle works. */@Component({ selector: 'ng-css-variables-example', templateUrl: './css-variables-example.component.html', changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule],})export class CssVariablesExampleComponent implements OnDestroy { protected readonly dark = signal(false);
cities = [ { value: 1, label: 'New York' }, { value: 2, label: 'London' }, { value: 3, label: 'Paris' }, { value: 4, label: 'Tokyo' }, ];
selectedCity = 1;
toggle(): void { this.dark.update((v) => !v); document.body.classList.toggle('ng-select-dark-demo', this.dark()); }
ngOnDestroy(): void { document.body.classList.remove('ng-select-dark-demo'); }}<p> No recompile, no <code>::ng-deep</code>, no extra selectors — the button below only flips a class on <code><body></code> that redeclares a handful of <code>--ng-select-*</code> custom properties. Open the dropdown while the dark palette is on to see it apply to the panel too.</p>
<button type="button" class="btn btn-secondary" (click)="toggle()"> {{ dark() ? 'Switch to light palette' : 'Switch to dark palette' }}</button>
<hr />
<ng-select [items]="cities" bindValue="value" bindLabel="label" [(ngModel)]="selectedCity" placeholder="Select a city"> </ng-select>
<hr />
<ng-select [items]="cities" bindValue="value" bindLabel="label" [multiple]="true" placeholder="Select cities"> </ng-select>Dark mode
Section titled “Dark mode”Because the variables are read at paint time, a dark palette is just a second declaration block:
@media (prefers-color-scheme: dark) { :root { --ng-select-bg: #1f2430; --ng-select-border: #3a4152; --ng-select-primary-text: #d8dee9; --ng-select-dropdown-bg: #1f2430; --ng-select-dropdown-option-text: #d8dee9; --ng-select-marked: #2a3145; --ng-select-selected: #2f3a52; }}For a class-driven toggle (the Tailwind dark: convention), swap the media query for html.dark { ... }.
Variables — default theme
Section titled “Variables — default theme”| Variable | Default | Applies to |
|---|---|---|
--ng-select-highlight |
#007eff |
Focused border |
--ng-select-primary-text |
#333 |
Control text |
--ng-select-disabled-text |
#f9f9f9 |
Disabled control background |
--ng-select-border |
#ccc |
Control and dropdown borders |
--ng-select-border-radius |
4px |
Control and dropdown corners |
--ng-select-bg |
#ffffff |
Control background |
--ng-select-height |
36px |
Control height |
--ng-select-box-shadow |
focus ring | Focused control shadow |
--ng-select-container-hover-shadow |
0 1px 0 rgba(0, 0, 0, 0.06) |
Control hover shadow |
--ng-select-value-padding-left |
10px |
Value container inset |
--ng-select-value-font-size |
0.9em |
Multi-select chip text |
--ng-select-value-text |
#333 |
Multi-select chip text |
--ng-select-input-text |
#000000 |
Search input text |
--ng-select-placeholder * |
#999999 |
Placeholder text |
--ng-select-selected |
rgb(234.6, 244.68, 255) |
Selected option / chip background |
--ng-select-selected-text |
#333 |
Selected option text |
--ng-select-selected-hover * |
rgb(209.1, 231.78, 255) |
Chip remove-icon hover |
--ng-select-selected-border * |
rgb(183.6, 218.88, 255) |
Chip remove-icon divider |
--ng-select-marked |
rgb(244.8, 249.84, 255) |
Keyboard-marked option background |
--ng-select-marked-text |
#333 |
Keyboard-marked option text |
--ng-select-arrow * |
#999999 |
Arrow and clear icon |
--ng-select-arrow-hover * |
#666666 |
Arrow hover |
--ng-select-arrow-active * |
#333333 |
Arrow while open |
--ng-select-clear * |
#999999 |
Clear icon |
--ng-select-clear-hover |
#d0021b |
Clear icon hover / focus |
--ng-select-border-dark * |
rgb(178.5, 178.5, 178.5) |
Top border while open |
--ng-select-border-light * |
rgb(216.75, 216.75, 216.75) |
Bottom border while open |
--ng-select-border-lighter * |
rgb(229.5, 229.5, 229.5) |
Panel edge merged with the control |
--ng-select-dropdown-bg |
#ffffff |
Panel background |
--ng-select-dropdown-border |
#ccc |
Panel border |
--ng-select-dropdown-shadow |
0 1px 0 rgba(0, 0, 0, 0.06) |
Panel shadow |
--ng-select-dropdown-option-bg |
#ffffff |
Option background |
--ng-select-dropdown-option-text |
rgba(0, 0, 0, 0.87) |
Option text |
--ng-select-dropdown-option-disabled |
#cccccc |
Disabled option text |
--ng-select-dropdown-optgroup-text |
rgba(0, 0, 0, 0.54) |
Group label text |
--ng-select-dropdown-optgroup-marked |
rgba(0, 0, 0, 0.54) |
Selected group label text |
* Derived shade — computed in Sass from a base colour. Set it explicitly when overriding that base colour through CSS variables.
Variables — ant.design theme
Section titled “Variables — ant.design theme”| Variable | Default | Applies to |
|---|---|---|
--ng-select-highlight |
#40a9ff |
Focused / open border |
--ng-select-primary-text |
rgba(0, 0, 0, 0.65) |
Control and option text |
--ng-select-disabled-text |
rgba(0, 0, 0, 0.25) |
Disabled text, arrow |
--ng-select-disabled-bg |
#f5f5f5 |
Disabled background |
--ng-select-border |
#d9d9d9 |
Control border |
--ng-select-border-radius |
4px |
Control, panel and option corners |
--ng-select-bg |
#ffffff |
Control and panel background |
--ng-select-selected |
rgba(24, 144, 255, 0.2) |
Focus ring |
--ng-select-selected-bg |
#fafafa |
Selected option background |
--ng-select-marked |
#e6f7ff |
Keyboard-marked option |
--ng-select-placeholder * |
rgba(153, 153, 153, 0.65) |
Placeholder text |
--ng-select-value-bg |
#fafafa |
Multi-select chip background |
--ng-select-value-border * |
rgb(232.3, 232.3, 232.3) |
Multi-select chip border |
--ng-select-border-lighter * |
rgb(242.5, 242.5, 242.5) |
Panel edge next to the control |
--ng-select-clear * |
#a6a6a6 |
Clear icon |
--ng-select-clear-bg |
rgba(0, 0, 0, 0.25) |
Clear button background |
--ng-select-clear-bg-hover |
rgba(0, 0, 0, 0.45) |
Clear button hover / focus |
--ng-select-clear-icon |
#fff |
Clear glyph |
--ng-select-dropdown-optgroup-text |
rgba(0, 0, 0, 0.45) |
Group label text |
--ng-select-dropdown-shadow |
0 2px 8px rgba(0, 0, 0, .15) |
Panel elevation |
* Derived shade — computed in Sass from a base colour. Set it explicitly when overriding that base colour through CSS variables.
Variables — material theme
Section titled “Variables — material theme”| Variable | Default | Applies to |
|---|---|---|
--ng-select-highlight |
#3f51b5 |
Focused underline, label, selection |
--ng-select-primary-text |
rgba(0, 0, 0, 0.87) |
Control and option text |
--ng-select-primary-light-text |
rgba(255, 255, 255, 0.87) |
Chip remove-icon hover |
--ng-select-secondary-text |
rgba(0, 0, 0, 0.54) |
Placeholder, arrow, clear, group |
--ng-select-secondary-light-text |
rgba(255, 255, 255, 0.54) |
Chip remove icon |
--ng-select-disabled-text |
rgba(0, 0, 0, 0.38) |
Disabled text |
--ng-select-disabled-value-text |
rgba(0, 0, 0, 0.26) |
Disabled chip text |
--ng-select-divider |
rgba(0, 0, 0, 0.12) |
Outline, selected row, panel shadow |
--ng-select-bg |
#ffffff |
Panel background, chip text |
--ng-select-underline |
rgba(0, 0, 0, 0.42) |
Resting underline |
--ng-select-marked |
rgba(0, 0, 0, 0.04) |
Keyboard-marked option |
--ng-select-fill-bg |
rgba(0, 0, 0, 0.06) |
ng-appearance-fill background |
--ng-select-fill-disabled-bg |
rgba(0, 0, 0, 0.02) |
Disabled fill background |
--ng-select-outline-width |
1px / 2px |
ng-appearance-outline border width |
Overriding with Sass
Section titled “Overriding with Sass”The Sass variables still work and seed the custom properties above, so existing setups keep compiling unchanged:
@use '@ng-select/ng-select/scss/default.theme' with ( $ng-select-highlight: #7aa2f7, $ng-select-border: #3a4152);The older @import form with variables declared beforehand also still works.
Unlike the CSS variables, Sass overrides do recompute the derived shades: setting $ng-select-highlight alone also recolours $ng-select-selected and $ng-select-marked, because Sass evaluates color.adjust() at build time. Reach for this when you want one base colour to drive the whole palette; reach for the CSS variables when you need the palette to change at runtime.
Overriding with selectors
Section titled “Overriding with selectors”For anything the variables do not cover, override the styles with increased selector specificity or create your own theme. This applies if you are using no ViewEncapsulation or adding styles to a global stylesheet.
The dropdown panel renders in a CDK overlay, not inside <ng-select>. Host class, [class], and [ngClass] values are mirrored onto the panel root so a single class can style both the closed control and the open list. For panel-only styling — or when host and panel need different classes — use panelClass:
<ng-select class="custom" panelClass="custom-panel"></ng-select>.ng-select.custom { border: 0px; min-height: 0px; border-radius: 0;}.ng-select.custom .ng-select-container { min-height: 0px; border-radius: 0;}.custom-panel.ng-dropdown-panel { max-height: 240px;}When a shared class is enough for both the control and the panel:
<ng-select class="custom"></ng-select>.ng-select.custom { border: 0px; min-height: 0px; border-radius: 0;}.ng-select.custom .ng-select-container { min-height: 0px; border-radius: 0;}.custom.ng-dropdown-panel .ng-option { padding-left: 20px;}If you are using ViewEncapsulation, you could use the special ::ng-deep selector which will prevent scoping for nested selectors, although this is more of a workaround and we recommend the solution described above.
.ng-select.custom ::ng-deep .ng-select-container { min-height: 0px; border-radius: 0;}Validation state
Section titled “Validation state”Signal Forms keeps validation state on its field tree. To apply the familiar ng-valid, ng-invalid, ng-touched, and ng-dirty classes to ng-select, enable Angular's compatibility preset:
import { provideSignalFormsConfig } from '@angular/forms/signals';import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
bootstrapApplication(AppComponent, { providers: [provideSignalFormsConfig({ classes: NG_STATUS_CLASSES })],});Reactive Forms and Template-driven Forms apply these classes automatically. Once the classes are enabled for the form system you use, show the error state with a custom style:
ng-select.ng-invalid.ng-touched .ng-select-container { border-color: #dc3545; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px #fde6e8;}