select

Displays a list of options for the user to pick from—triggered by a button.

PreviousNext
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-preview',
  imports: [ZardSelectImports],
  template: `
    <z-select class="w-full min-w-48" zPlaceholder="Select a fruit" [(zValue)]="selectedFruit">
      <z-select-label>Fruits</z-select-label>
      <z-select-item zValue="apple">Apple</z-select-item>
      <z-select-item zValue="banana">Banana</z-select-item>
      <z-select-item zValue="blueberry">Blueberry</z-select-item>
      <z-select-item zValue="grapes">Grapes</z-select-item>
      <z-select-item zValue="pineapple">Pineapple</z-select-item>
    </z-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectPreviewComponent {
  readonly selectedFruit = signal('');
}

Installation

Copy
npx zard-cli@latest add select

Usage

import { ZardSelectImports } from '@/shared/components/select/select.imports';
Copy
<z-select zPlaceholder="Select a fruit">
  <z-select-item zValue="apple">Apple</z-select-item>
  <z-select-item zValue="banana">Banana</z-select-item>
  <z-select-item zValue="blueberry">Blueberry</z-select-item>
</z-select>
Copy

Composition

Use the following composition to build a select:

z-select
├── z-select-label
├── z-select-item
├── z-select-group
│   ├── z-select-label
│   ├── z-select-item
│   └── z-select-item
├── z-select-separator
└── z-select-group
    ├── z-select-label
    ├── z-select-item
    └── z-select-item
Copy

Examples

align item with trigger

[zPosition] controls how the listbox opens: 'item-aligned' (the default) aligns the selected item over the trigger; 'popper' drops the listbox from the trigger's edge instead. Toggle the switch to compare the two.

Toggle to align the item with the trigger.

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardSelectImports, type ZardSelectPositionVariants } from '@/shared/components/select';
import { ZardSwitchComponent } from '@/shared/components/switch';

@Component({
  selector: 'z-demo-select-align-item-with-trigger',
  imports: [ZardSelectImports, ZardSwitchComponent, ...ZardFieldImports],
  template: `
    <div z-field-group class="w-full min-w-xs">
      <div z-field zOrientation="horizontal">
        <div z-field-content>
          <label z-field-label for="align-item">Align Item</label>
          <p z-field-description>Toggle to align the item with the trigger.</p>
        </div>
        <z-switch id="align-item" [(zChecked)]="alignItem" />
      </div>

      <div z-field>
        <z-select [zPosition]="position()" [(zValue)]="selectedFruit">
          <z-select-group>
            <z-select-item zValue="apple">Apple</z-select-item>
            <z-select-item zValue="banana">Banana</z-select-item>
            <z-select-item zValue="blueberry">Blueberry</z-select-item>
            <z-select-item zValue="grapes">Grapes</z-select-item>
            <z-select-item zValue="pineapple">Pineapple</z-select-item>
          </z-select-group>
        </z-select>
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectAlignItemWithTriggerComponent {
  readonly alignItem = signal(true);
  readonly selectedFruit = signal('banana');

  protected readonly position = computed<ZardSelectPositionVariants>(() =>
    this.alignItem() ? 'item-aligned' : 'popper',
  );
}

groups

Group items with z-select-group, label each group with z-select-label, and divide groups with z-select-separator.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-groups',
  imports: [ZardSelectImports],
  template: `
    <z-select class="w-full min-w-48" zPlaceholder="Select a fruit" [(zValue)]="selectedFood">
      <z-select-group>
        <z-select-label>Fruits</z-select-label>
        <z-select-item zValue="apple">Apple</z-select-item>
        <z-select-item zValue="banana">Banana</z-select-item>
        <z-select-item zValue="blueberry">Blueberry</z-select-item>
        <z-select-item zValue="grapes">Grapes</z-select-item>
      </z-select-group>
      <z-select-separator />
      <z-select-group>
        <z-select-label>Vegetables</z-select-label>
        <z-select-item zValue="aubergine">Aubergine</z-select-item>
        <z-select-item zValue="broccoli">Broccoli</z-select-item>
        <z-select-item zValue="carrot">Carrot</z-select-item>
        <z-select-item zValue="courgette">Courgette</z-select-item>
      </z-select-group>
    </z-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectGroupsComponent {
  readonly selectedFood = signal('');
}

scrollable

A select with enough items to require scrolling. Arrow keys, Home/End and Page Up/Down move the highlight and keep it scrolled into view.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-scrollable',
  imports: [ZardSelectImports],
  template: `
    <z-select class="w-full min-w-64" zPlaceholder="Select a timezone" zPosition="popper" [(zValue)]="selectedTimezone">
      <z-select-group>
        <z-select-label>North America</z-select-label>
        <z-select-item zValue="est">Eastern Standard Time (EST)</z-select-item>
        <z-select-item zValue="cst">Central Standard Time (CST)</z-select-item>
        <z-select-item zValue="mst">Mountain Standard Time (MST)</z-select-item>
        <z-select-item zValue="pst">Pacific Standard Time (PST)</z-select-item>
        <z-select-item zValue="akst">Alaska Standard Time (AKST)</z-select-item>
        <z-select-item zValue="hst">Hawaii Standard Time (HST)</z-select-item>
      </z-select-group>
      <z-select-separator />
      <z-select-group>
        <z-select-label>Europe & Africa</z-select-label>
        <z-select-item zValue="gmt">Greenwich Mean Time (GMT)</z-select-item>
        <z-select-item zValue="cet">Central European Time (CET)</z-select-item>
        <z-select-item zValue="eet">Eastern European Time (EET)</z-select-item>
        <z-select-item zValue="west">Western European Summer Time (WEST)</z-select-item>
        <z-select-item zValue="cat">Central Africa Time (CAT)</z-select-item>
        <z-select-item zValue="eat">East Africa Time (EAT)</z-select-item>
      </z-select-group>
      <z-select-separator />
      <z-select-group>
        <z-select-label>Asia</z-select-label>
        <z-select-item zValue="msk">Moscow Time (MSK)</z-select-item>
        <z-select-item zValue="ist">India Standard Time (IST)</z-select-item>
        <z-select-item zValue="cst_china">China Standard Time (CST)</z-select-item>
        <z-select-item zValue="jst">Japan Standard Time (JST)</z-select-item>
        <z-select-item zValue="kst">Korea Standard Time (KST)</z-select-item>
        <z-select-item zValue="ist_indonesia">Indonesia Central Standard Time (WITA)</z-select-item>
      </z-select-group>
      <z-select-separator />
      <z-select-group>
        <z-select-label>Australia & Pacific</z-select-label>
        <z-select-item zValue="awst">Australian Western Standard Time (AWST)</z-select-item>
        <z-select-item zValue="acst">Australian Central Standard Time (ACST)</z-select-item>
        <z-select-item zValue="aest">Australian Eastern Standard Time (AEST)</z-select-item>
        <z-select-item zValue="nzst">New Zealand Standard Time (NZST)</z-select-item>
        <z-select-item zValue="fjt">Fiji Time (FJT)</z-select-item>
      </z-select-group>
    </z-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectScrollableComponent {
  readonly selectedTimezone = signal('');
}

disabled

[zDisabled] disables the trigger; the select cannot open, receive focus, or change its value.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-disabled',
  imports: [ZardSelectImports],
  template: `
    <z-select class="w-full min-w-48" zPlaceholder="Select a fruit" [(zValue)]="selectedFruit" zDisabled>
      <z-select-item zValue="apple">Apple</z-select-item>
      <z-select-item zValue="banana">Banana</z-select-item>
      <z-select-item zValue="blueberry">Blueberry</z-select-item>
      <z-select-item zValue="grapes">Grapes</z-select-item>
      <z-select-item zValue="pineapple">Pineapple</z-select-item>
    </z-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectDisabledComponent {
  readonly selectedFruit = signal('');
}

invalid

[zInvalid] sets aria-invalid on the trigger and applies destructive styling. Composed here inside z-field with data-invalid and a z-field-error for the full validation pattern.
Please select a fruit.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-invalid',
  imports: [...ZardFieldImports, ZardSelectImports],
  template: `
    <div z-field class="w-full min-w-48" data-invalid="true">
      <label z-field-label for="select-invalid">Fruit</label>
      <z-select id="select-invalid" zPlaceholder="Select a fruit" zInvalid [(zValue)]="selectedFruit">
        <z-select-item zValue="apple">Apple</z-select-item>
        <z-select-item zValue="banana">Banana</z-select-item>
        <z-select-item zValue="blueberry">Blueberry</z-select-item>
        <z-select-item zValue="grapes">Grapes</z-select-item>
        <z-select-item zValue="pineapple">Pineapple</z-select-item>
      </z-select>
      <z-field-error>Please select a fruit.</z-field-error>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectInvalidComponent {
  readonly selectedFruit = signal('');
}

multi select

[zMultiple] turns the select into a multiselect; [(zValue)] becomes a string[] and each pick renders as a removable z-badge in the trigger. [zMaxLabelCount] caps how many badges show before collapsing the rest into a "N more items selected" badge.

Selected fruits:

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-multi-select',
  imports: [ZardSelectImports],
  template: `
    <div class="flex h-100 w-75 flex-col gap-4">
      <p class="text-muted-foreground text-sm">Selected fruits: {{ selectedValues().join(', ') }}</p>
      <z-select
        zPlaceholder="Select multiple fruits"
        [zMultiple]="true"
        [zMaxLabelCount]="3"
        [(zValue)]="selectedValues"
      >
        <z-select-item zValue="apple">Apple</z-select-item>
        <z-select-item zValue="banana">Banana</z-select-item>
        <z-select-item zValue="blueberry">Blueberry</z-select-item>
        <z-select-item zValue="grapes">Grapes</z-select-item>
        <z-select-item zValue="pineapple">Pineapple</z-select-item>
        <z-select-item zValue="strawberry">Strawberry</z-select-item>
        <z-select-item zValue="watermelon">Watermelon</z-select-item>
        <z-select-item zValue="kiwi">Kiwi</z-select-item>
        <z-select-item zValue="mango">Mango</z-select-item>
      </z-select>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectMultiSelectComponent {
  readonly selectedValues = signal<string[]>([]);
}

reactive forms

formControlName binds through z-select's ControlValueAccessor, for both a single select and a zMultiple one — the multiselect control just carries a string[] value.

Fruit: banana · Toppings: chocolate, nuts

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardSelectImports } from '@/shared/components/select/select.imports';

@Component({
  selector: 'z-demo-select-reactive-forms',
  imports: [...ZardSelectImports, ...ZardFieldImports, ReactiveFormsModule],
  template: `
    <form [formGroup]="form">
      <div z-field-group class="w-full min-w-xs">
        <div z-field>
          <label z-field-label for="reactive-select-fruit">Favorite fruit</label>
          <z-select id="reactive-select-fruit" formControlName="fruit" zPlaceholder="Select a fruit">
            <z-select-item zValue="apple">Apple</z-select-item>
            <z-select-item zValue="banana">Banana</z-select-item>
            <z-select-item zValue="blueberry">Blueberry</z-select-item>
            <z-select-item zValue="grapes">Grapes</z-select-item>
            <z-select-item zValue="pineapple">Pineapple</z-select-item>
          </z-select>
        </div>
        <div z-field>
          <label z-field-label for="reactive-select-toppings">Toppings</label>
          <z-select id="reactive-select-toppings" formControlName="toppings" zPlaceholder="Select toppings" zMultiple>
            <z-select-item zValue="chocolate">Chocolate</z-select-item>
            <z-select-item zValue="caramel">Caramel</z-select-item>
            <z-select-item zValue="nuts">Nuts</z-select-item>
            <z-select-item zValue="sprinkles">Sprinkles</z-select-item>
          </z-select>
        </div>
        <p class="text-muted-foreground text-sm">
          Fruit: {{ form.controls.fruit.value || 'none' }} · Toppings:
          {{ form.controls.toppings.value.length ? form.controls.toppings.value.join(', ') : 'none' }}
        </p>
      </div>
    </form>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectReactiveFormsComponent {
  private readonly fb = inject(FormBuilder);

  protected readonly form = this.fb.nonNullable.group({
    fruit: ['banana'],
    toppings: [['chocolate', 'nuts'] as string[]],
  });
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.
// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardSelectImports } from '@/shared/components/select/select.imports';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

interface SelectRtlValues {
  selectFruit: string;
  fruits: string;
  apple: string;
  banana: string;
  blueberry: string;
  grapes: string;
  pineapple: string;
  vegetables: string;
  carrot: string;
  broccoli: string;
  spinach: string;
}

const translations: ZardRtlTranslations<SelectRtlValues> = {
  en: {
    dir: 'ltr',
    values: {
      selectFruit: 'Select a fruit',
      fruits: 'Fruits',
      apple: 'Apple',
      banana: 'Banana',
      blueberry: 'Blueberry',
      grapes: 'Grapes',
      pineapple: 'Pineapple',
      vegetables: 'Vegetables',
      carrot: 'Carrot',
      broccoli: 'Broccoli',
      spinach: 'Spinach',
    },
  },
  ar: {
    dir: 'rtl',
    values: {
      selectFruit: 'اختر فاكهة',
      fruits: 'الفواكه',
      apple: 'تفاح',
      banana: 'موز',
      blueberry: 'توت أزرق',
      grapes: 'عنب',
      pineapple: 'أناناس',
      vegetables: 'الخضروات',
      carrot: 'جزر',
      broccoli: 'بروكلي',
      spinach: 'سبانخ',
    },
  },
  he: {
    dir: 'rtl',
    values: {
      selectFruit: 'בחר פרי',
      fruits: 'פירות',
      apple: 'תפוח',
      banana: 'בננה',
      blueberry: 'אוכמניה',
      grapes: 'ענבים',
      pineapple: 'אננס',
      vegetables: 'ירקות',
      carrot: 'גזר',
      broccoli: 'ברוקולי',
      spinach: 'תרד',
    },
  },
};

@Component({
  selector: 'z-demo-select-rtl',
  imports: [ZardSelectImports],
  template: `
    <z-select class="w-48" [zPlaceholder]="i18n.t().selectFruit" [(zValue)]="selected">
      <z-select-group>
        <z-select-label>{{ i18n.t().fruits }}</z-select-label>
        <z-select-item zValue="apple">{{ i18n.t().apple }}</z-select-item>
        <z-select-item zValue="banana">{{ i18n.t().banana }}</z-select-item>
        <z-select-item zValue="blueberry">{{ i18n.t().blueberry }}</z-select-item>
        <z-select-item zValue="grapes">{{ i18n.t().grapes }}</z-select-item>
        <z-select-item zValue="pineapple">{{ i18n.t().pineapple }}</z-select-item>
      </z-select-group>
      <z-select-separator />
      <z-select-group>
        <z-select-label>{{ i18n.t().vegetables }}</z-select-label>
        <z-select-item zValue="carrot">{{ i18n.t().carrot }}</z-select-item>
        <z-select-item zValue="broccoli">{{ i18n.t().broccoli }}</z-select-item>
        <z-select-item zValue="spinach">{{ i18n.t().spinach }}</z-select-item>
      </z-select-group>
    </z-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSelectRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);

  protected readonly selected = signal('');
}

API Reference

z-selectComponent

A customizable select component that supports single and multiple value selection.

PropertyDescriptionTypeDefault
[zValue] Selected value(s), two-way bindable — a plain `string`, or `string[]` when `zMultiple` is set string | string[] ''
[class] Custom CSS classes ClassValue ''
[zAlign] Overlay alignment relative to the trigger 'start' | 'center' | 'end' 'center'
[zDisabled] Disables the select boolean false
[zInvalid] Applies invalid ARIA state and destructive styling boolean false
[zLabel] Optional manual override for the displayed label string ''
[zMaxLabelCount] In multiselect mode, how many selected labels render as badges before collapsing the rest into a "N more items selected" badge number 1
[zMultiple] Turns the select into a multiselect; `zValue` becomes a `string[]` and each pick renders as a `z-badge` boolean false
[zPlaceholder] Placeholder text shown when nothing is selected string 'Select an option...'
[zPosition] Overlay positioning mode: 'item-aligned' opens the listbox so the selected item sits over the trigger; 'popper' drops it from the trigger's edge instead 'item-aligned' | 'popper' 'item-aligned'
[(zValue)] Selected value string | string[] '' | []
(zSelectionChange) Emitted when the selected value changes EventEmitter<string | string[]> -

z-select-itemComponent

Represents an individual item inside a z-select component.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''
[zValue] The value associated with this item (required) string -
[zDisabled] Disables selection for this item boolean false

z-select-groupComponent

Groups related select items.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

z-select-labelComponent

Displays a non-selectable label inside a select group.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''

z-select-separatorComponent

Displays a separator between select groups.

PropertyDescriptionTypeDefault
[class] Custom CSS classes ClassValue ''
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .