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

Import

component.ts
import { NeuListboxComponent } from '@neural-ui/native/listbox';

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.

A shared review

Select visible teams and prepare a local review summary. An archived team remains unavailable and the summary uses names rather than internal IDs.

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 { NeuListboxComponent } from '@neural-ui/native/listbox';
import { View, Text, ScrollView } from '@ng-native/components';
import type { NeuOption } from '@neural-ui/native';

@Component({
  selector: 'app-example',
  imports: [NeuButtonComponent, NeuAlertComponent, NeuListboxComponent, 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">A shared review</text><text class="product-caption">Choose everyone who needs a seat at the table</text></view>
    <view class="product-surface"><neu-listbox label="Invite teams" [options]="teams" [multiple]="true" [(value)]="selected" /></view>
    <view class="product-section"><text class="product-heading">Review participants</text><text class="product-caption">{{ selectedLabels() || 'Choose at least one team' }}</text>
    <view class="product-actions"><neu-button label="Prepare review" size="sm" [disabled]="!selected().length" (pressed)="prepared.set(selectedLabels())" /></view>
    @if (prepared()) { <neu-alert title="Review prepared" [message]="prepared()" tone="success" /> }</view></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 selected = signal<readonly string[]>(['design']);
  readonly selectedLabels = computed(() => this.teams.filter(team => this.selected().includes(team.id)).map(team => team.label).join(', '));
  readonly prepared = signal('');
}

Single selection

Choose one default team and check its ID below.

View and copy the complete example
single-selection.component.ts
import { Component, signal } from '@angular/core';
import { NeuListboxComponent } from '@neural-ui/native/listbox';
import { View, Text, ScrollView } from '@ng-native/components';
import type { NeuOption } from '@neural-ui/native';

@Component({
  selector: 'app-example',
  imports: [NeuListboxComponent, View, Text, ScrollView],
  template: `
    <scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
    <neu-listbox label="Default team" [options]="teams" [(value)]="defaultTeam" [multiple]="false" /><text class="usage-caption">Default: {{ defaultTeam()[0] || '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; }


  `,
})
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 defaultTeam = signal<readonly string[]>(['design']);
}

Custom option content

Replace each option’s visual content while Listbox keeps the radio or checkbox behavior and its selection mark.

View and copy the complete example
custom-items.component.ts
import { Component, signal } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuListboxComponent } from '@neural-ui/native/listbox';
import { View, Text, ScrollView } from '@ng-native/components';
import { NeuListboxItemDirective } from '@neural-ui/native/listbox';

@Component({
  selector: 'app-example',
  imports: [NeuAvatarComponent, NeuListboxComponent, NeuListboxItemDirective, View, Text, ScrollView],
  template: `
    <scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
    <neu-listbox label="Release team" [options]="teams" [(value)]="selected">
      <ng-template neuListboxItem let-option let-index="index" let-selected="selected">
        <view class="team-option"><neu-avatar [label]="option.label" size="sm" /><text [class]="selected ? 'selected-option' : 'usage-heading'">{{ index + 1 }}. {{ option.label }}</text></view>
      </ng-template>
    </neu-listbox>
    <text class="usage-caption">Selected ID: {{ selected()[0] || '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 { flex-direction: row; align-items: center; gap: 10px; } .selected-option { color: var(--neu-on-accent, #ffffff); font-weight: 600; }
  `,
})
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 selected = signal<readonly string[]>(['design']);
}
Interactive playground

Change the settings and try the result.

Device captures shown in English.

Listbox · android · native capture
AndroidNative screenshot
Listbox · 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 { NeuListboxItemDirective } from '@neural-ui/native/listbox';
Template or regionContextPurpose
ng-template[neuListboxItem]$implicit: NeuOption; index: number; selected: boolean; disabled: booleanReplaces option content. Listbox keeps its selection mark and radio or checkbox action. index follows the displayed options.

Template contexts

template-contexts.ts
import type { NeuListboxItemContext } from '@neural-ui/native/listbox';

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.

Reusable templates

You can also declare <ng-template #row let-item> and pass [itemTemplate]="row". An explicit itemTemplate takes precedence over the projected directive; if neither is supplied, the default content is shown.

API reference

Inputs

NameTypeDefaultBindingDescription
labelstringRequiredInputHuman-readable name for the control, action or group.
optionsreadonly NeuOption[][]InputAvailable choices. Selected values refer to option IDs, not their displayed labels.
appearance"cards" | "plain"'cards'InputOption surface: cards uses separate filled choices; plain uses compact list rows. Select uses plain inside its dropdown. Selection, disabled state and templates work in both appearances.
itemTemplateTemplateRef<NeuListboxItemContext> | undefinedundefinedInputReusable Angular option template. Its context provides the original option, displayed index, selected and disabled. This explicit input takes precedence over neuListboxItem; Listbox retains selection and accessibility.
multiplebooleanfalseInputAllows multiple choices or files where supported; check the declared model type.
disabledbooleanfalseInputDisables user interaction. Values can still be supplied by the application.
readonlybooleanfalseInputPrevents user editing or selection while retaining the current value.
emptyLabelstring'No options'InputText or accessible name used for empty state. It does not perform the action by itself.

Models

NameTypeDefaultBindingDescription
valuereadonly string[][]Model · two-wayCurrent edited value or selected ID(s), using the declared type. Bind a writable signal with [(value)].

Outputs

NamePayloadWhen it emits
valueChangereadonly string[]Emits the updated value value. [(value)] binds this output together with its model input.
selectedstringEmits the selected item ID.
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 value = signal<readonly 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.

NeuListboxItemContext

NeuListboxItemContext.ts
import type { NeuListboxItemContext } from '@neural-ui/native/listbox';
import type { NeuOption } from '@neural-ui/native';

// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuListboxItemContext {
    readonly $implicit: NeuOption;
    /** Position in the displayed options, after filtering. */
    readonly index: number;
    readonly selected: boolean;
    readonly disabled: boolean;
}
FieldTypeRequirementMeaning
$implicitNeuOptionRequiredOriginal item supplied to the row template; available as let-item.
indexnumberRequiredZero-based position in the displayed collection; available as let-index="index". The personalization section describes how this component handles filtering, sorting or recycled rows.
selectedbooleanRequiredWhether this option is selected in its template context.
disabledbooleanRequiredPrevents activating or selecting this item.