Skip to main content
Neural Nativev0.1.0-beta.1Documentation

Import

component.ts
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
basic.component.ts
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
custom-templates.component.ts
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');
}
Interactive playground

Change the settings and try the result.

Device captures shown in English.

Select · android · native capture
AndroidNative screenshot
Select · ios · native capture
iOSNative screenshot

Personalization

Import these directives together with the component and add them to Component.imports. The examples above include the complete setup.

personalization-imports.ts
import { NeuSelectItemDirective, NeuSelectSelectedDirective } from '@neural-ui/native/select';
Template or regionContextPurpose
ng-template[neuSelectItem]$implicit: NeuOption; index: number; selected: boolean; disabled: booleanCustom 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: stringCustom control content, including its empty state. Options keep their original order; in multiple mode use values, and in single mode use value.

Template contexts

template-contexts.ts
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

NameTypeDefaultBindingDescription
optionsreadonly NeuOption[][]InputAvailable choices. Selected values refer to option IDs, not their displayed labels.
placeholderstring'Choose'InputExample or prompt displayed while the value is empty; keep a separate label.
closeLabelstring'Dismiss options'InputText or accessible name used for close. It does not perform the action by itself.
multiplebooleanfalseInputUse checkboxes and keep the dropdown open for several choices. Bind values in this mode; value is reserved for single selection.
searchablebooleanfalseInputShow a search field inside the dropdown. Matches labels and descriptions without accent or case sensitivity. Search resets when reopened.
searchLabelstring'Search options'InputAccessible name of the dropdown search field. It is not shown as a separate heading; searchPlaceholder supplies the visible prompt inside the field.
searchPlaceholderstring'Type to filter'InputPlaceholder shown while the dropdown search is empty.
emptyLabelstring'No matching options'InputText or accessible name used for empty state. It does not perform the action by itself.
labelstringRequiredInputHuman-readable name for the control, action or group.
disabledbooleanfalseInputDisables user interaction. Values can still be supplied by the application.
readonlybooleanfalseInputPrevents user editing or selection while retaining the current value.
invalidbooleanfalseInputMarks the control invalid. Your form or application supplies the validation rule.
touchedbooleanfalseInputField interaction state, supplied manually or through Signal Forms.
hintstring''InputField guidance shown when no error message is being displayed.
requiredbooleanfalseInputRequired-field presentation. Define the actual validation rule in your application form.
requiredLabelstring'Required'InputLocalized requirement announced with the field hint when required is true. Defaults to Required; it does not validate the value.
errorstring''InputApplication-provided error message; combine it with the control’s invalid state.

Models

NameTypeDefaultBindingDescription
valuesreadonly string[][]Model · two-waySelected option IDs in multiple mode. Filtering preserves selections outside the results. Single mode uses value instead.
valuestring''Model · two-waySelected option ID in single mode. Multiple mode uses the independent values array.

Outputs

NamePayloadWhen it emits
valuesChangereadonly string[]Emits the updated values value. [(values)] binds this output together with its model input.
valueChangestringEmits the updated value value. [(value)] binds this output together with its model input.
touchvoidReports user interaction to the surrounding form or application.

Model state

Bind the signal with [(modelName)] without calling it. Use modelName() to read its value.

model-fields.ts
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

NeuOption.ts
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;
}
FieldTypeRequirementMeaning
idstringRequiredStable, unique identifier used for tracking and selection.
labelstringRequiredHuman-readable item name.
descriptionstringOptionalOptional supporting text; rendering depends on the control.
disabledbooleanOptionalPrevents activating or selecting this item.