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[]],
  });
}

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 .