Selection
Select
Choose a team, reopen the picker and preserve the selection.
Import
import { NeuSelectComponent } from '@neural-ui/native/select';Examples
Import ExampleComponent into its parent and place <app-example /> inside your application’s root neu-theme. The preview supplies the device frame and safe areas; use the root setup from Installation in your app.
Bring the right people together
Compare single and multiple selection in a project invitation. Search narrows the list, labels explain the selected teams and a preview snapshots the invitation locally.
View and copy the complete example
import { Component, signal, computed } from '@angular/core';
import { NeuButtonComponent } from '@neural-ui/native/button';
import { NeuAlertComponent } from '@neural-ui/native/alert';
import { NeuSelectComponent } from '@neural-ui/native/select';
import { View, Text, ScrollView } from '@ng-native/components';
import type { NeuOption } from '@neural-ui/native';
@Component({
selector: 'app-example',
imports: [NeuButtonComponent, NeuAlertComponent, NeuSelectComponent, View, Text, ScrollView],
template: `
<scroll-view class="product-scroll" keyboardShouldPersistTaps="handled" [showsVerticalScrollIndicator]="true"><view class="product-stack"><view class="product-section"><text class="product-title" accessibilityRole="header">Bring the right people together</text><text class="product-caption">One owner, several invited teams</text></view>
<view class="product-surface"><neu-select label="Team" [options]="teams" [(value)]="team" [searchable]="true" placeholder="Choose a team" />
<text class="product-caption">Responsible team: {{ teamLabel() }}</text></view>
<view class="product-surface"><neu-select label="Invite teams" [options]="teams" [multiple]="true" [searchable]="true" [(values)]="invitedTeams" placeholder="Choose teams" />
<text class="product-caption">{{ invitedLabels() || 'No teams invited yet' }}</text>
<view class="product-actions"><neu-button label="Preview invitation" size="sm" variant="outline" [disabled]="!invitedTeams().length" (pressed)="invitation.set(invitedLabels())" /></view></view>
@if (invitation()) { <neu-alert title="Invitation preview" [message]="invitation()" tone="info" /> }</view></scroll-view>
`,
styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }
:host { flex: 1; flex-shrink: 1; min-height: 0; }
.product-scroll { flex: 1; flex-shrink: 1; height: 0px; min-height: 0; }
.product-stack { gap: 24px; padding-bottom: 20px; }
.product-section { gap: 12px; }
.product-row { flex-direction: row; align-items: center; gap: 12px; }
.product-copy { flex: 1; min-width: 0; gap: 4px; }
.product-title { color: var(--neu-text, #172033); font-size: 24px; line-height: 30px; font-weight: 600; }
.product-heading { color: var(--neu-text, #172033); font-size: 17px; line-height: 23px; font-weight: 600; }
.product-caption { color: var(--neu-muted, #596477); font-size: 14px; line-height: 20px; }
.product-actions { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px; }
.product-surface { gap: 16px; padding: 16px; border-radius: 16px; background-color: var(--neu-surface, #ffffff); border-width: 1px; border-color: var(--neu-border, #a5afbf); }
`,
})
export class ExampleComponent {
readonly teams: readonly NeuOption[] = [{ id: 'design', label: 'Design', description: 'Product interfaces' }, { id: 'engineering', label: 'Engineering', description: 'Application development' }, { id: 'archive', label: 'Archived team', disabled: true }];
readonly team = signal('design');
readonly invitedTeams = signal<readonly string[]>(['design']);
readonly teamLabel = computed(() => this.teams.find(team => team.id === this.team())?.label || '');
readonly invitedLabels = computed(() => this.teams.filter(team => this.invitedTeams().includes(team.id)).map(team => team.label).join(', '));
readonly invitation = signal('');
}Option and selected-value templates
Customize option content and the selected value. Select retains searching, disabled options and selection behavior.
View and copy the complete example
import { Component, signal } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuSelectComponent } from '@neural-ui/native/select';
import { View, Text, ScrollView } from '@ng-native/components';
import { NeuSelectItemDirective, NeuSelectSelectedDirective } from '@neural-ui/native/select';
@Component({
selector: 'app-example',
imports: [NeuAvatarComponent, NeuSelectComponent, NeuSelectItemDirective, NeuSelectSelectedDirective, View, Text, ScrollView],
template: `
<scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
<neu-select label="Project team" [options]="teams" [(value)]="team" [searchable]="true" placeholder="Choose a team">
<ng-template neuSelectItem let-option let-selected="selected" let-disabled="disabled">
<view class="team-option"><neu-avatar [label]="option.label" size="sm" /><view class="team-copy"><text [class]="selected ? 'team-selected' : 'usage-heading'">{{ option.label }}</text><text [class]="selected ? 'team-selected-caption' : 'usage-caption'">{{ disabled ? 'Unavailable' : option.description }}</text></view></view>
</ng-template>
<ng-template neuSelectSelected let-options let-placeholder="placeholder">
@if (options.length) { <view class="selected-team"><neu-avatar [label]="options[0].label" size="sm" /><text>{{ options[0].label }}</text></view> }
@else { <text>{{ placeholder }}</text> }
</ng-template>
</neu-select>
<text class="usage-caption">Selected team ID: {{ team() || 'None' }}</text>
</view></scroll-view>
`,
styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }
.usage-scroll { flex: 1; }
.usage-stack { gap: 20px; padding-bottom: 20px; }
.usage-section { gap: 12px; }
.usage-heading { color: var(--neu-text, #172033); font-size: 16px; font-weight: 600; }
.usage-caption { color: var(--neu-muted, #64748b); font-size: 14px; }
.usage-row { flex-direction: row; flex-wrap: wrap; gap: 12px; align-items: center; }
.usage-actions { flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center; }
.team-option, .selected-team { flex-direction: row; gap: 10px; align-items: center; } .team-copy { flex: 1; min-width: 0; gap: 3px; } .team-selected { color: var(--neu-accent, #006fe6); font-weight: 600; } .team-selected-caption { color: var(--neu-muted, #596477); font-size: 14px; }
`,
})
export class ExampleComponent {
readonly teams = [
{ id: 'design', label: 'Design', description: 'Product interfaces' },
{ id: 'engineering', label: 'Engineering', description: 'Mobile applications' },
{ id: 'archive', label: 'Archived team', description: 'No new members', disabled: true },
];
readonly team = signal('design');
}Change the settings and try the result.
Device captures shown in English.


Personalization
Import these directives together with the component and add them to Component.imports. The examples above include the complete setup.
import { NeuSelectItemDirective, NeuSelectSelectedDirective } from '@neural-ui/native/select';| Template or region | Context | Purpose |
|---|---|---|
ng-template[neuSelectItem] | $implicit: NeuOption; index: number; selected: boolean; disabled: boolean | Custom option visuals. The surrounding row keeps selection, its accessible label and its checked state. index follows the filtered options. |
ng-template[neuSelectSelected] | $implicit: readonly NeuOption[]; value: string; values: readonly string[]; placeholder: string | Custom control content, including its empty state. Options keep their original order; in multiple mode use values, and in single mode use value. |
Template contexts
import type { NeuSelectItemContext, NeuSelectSelectedContext } from '@neural-ui/native/select';let-item reads $implicit; named fields use let-index="index". Use the exported context types for reusable TemplateRef declarations. Keep nested content presentational: the parent retains its accessible action and selection behavior.
API reference
Inputs
| Name | Type | Default | Binding | Description |
|---|---|---|---|---|
| options | readonly NeuOption[] | [] | Input | Available choices. Selected values refer to option IDs, not their displayed labels. |
| placeholder | string | 'Choose' | Input | Example or prompt displayed while the value is empty; keep a separate label. |
| closeLabel | string | 'Dismiss options' | Input | Text or accessible name used for close. It does not perform the action by itself. |
| multiple | boolean | false | Input | Use checkboxes and keep the dropdown open for several choices. Bind values in this mode; value is reserved for single selection. |
| searchable | boolean | false | Input | Show a search field inside the dropdown. Matches labels and descriptions without accent or case sensitivity. Search resets when reopened. |
| searchLabel | string | 'Search options' | Input | Accessible name of the dropdown search field. It is not shown as a separate heading; searchPlaceholder supplies the visible prompt inside the field. |
| searchPlaceholder | string | 'Type to filter' | Input | Placeholder shown while the dropdown search is empty. |
| emptyLabel | string | 'No matching options' | Input | Text or accessible name used for empty state. It does not perform the action by itself. |
| label | string | Required | Input | Human-readable name for the control, action or group. |
| disabled | boolean | false | Input | Disables user interaction. Values can still be supplied by the application. |
| readonly | boolean | false | Input | Prevents user editing or selection while retaining the current value. |
| invalid | boolean | false | Input | Marks the control invalid. Your form or application supplies the validation rule. |
| touched | boolean | false | Input | Field interaction state, supplied manually or through Signal Forms. |
| hint | string | '' | Input | Field guidance shown when no error message is being displayed. |
| required | boolean | false | Input | Required-field presentation. Define the actual validation rule in your application form. |
| requiredLabel | string | 'Required' | Input | Localized requirement announced with the field hint when required is true. Defaults to Required; it does not validate the value. |
| error | string | '' | Input | Application-provided error message; combine it with the control’s invalid state. |
Models
| Name | Type | Default | Binding | Description |
|---|---|---|---|---|
| values | readonly string[] | [] | Model · two-way | Selected option IDs in multiple mode. Filtering preserves selections outside the results. Single mode uses value instead. |
| value | string | '' | Model · two-way | Selected option ID in single mode. Multiple mode uses the independent values array. |
Outputs
| Name | Payload | When it emits |
|---|---|---|
| valuesChange | readonly string[] | Emits the updated values value. [(values)] binds this output together with its model input. |
| valueChange | string | Emits the updated value value. [(value)] binds this output together with its model input. |
| touch | void | Reports user interaction to the surrounding form or application. |
Model state
Bind the signal with [(modelName)] without calling it. Use modelName() to read its value.
import { signal } from '@angular/core';
// Fields inside your component class
readonly values = signal<readonly string[]>([]);
readonly value = signal<string>('');Types and data interfaces
Use the imports in your application. The definitions below are provided for reference.
NeuOption
import type { NeuOption } from '@neural-ui/native';
// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuOption {
readonly id: string;
readonly label: string;
readonly description?: string;
readonly disabled?: boolean;
}| Field | Type | Requirement | Meaning |
|---|---|---|---|
| id | string | Required | Stable, unique identifier used for tracking and selection. |
| label | string | Required | Human-readable item name. |
| description | string | Optional | Optional supporting text; rendering depends on the control. |
| disabled | boolean | Optional | Prevents activating or selecting this item. |