Selection
Listbox
Browse a visible list of options and keep the selection clear.
Import
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
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
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
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']);
}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 { NeuListboxItemDirective } from '@neural-ui/native/listbox';| Template or region | Context | Purpose |
|---|---|---|
ng-template[neuListboxItem] | $implicit: NeuOption; index: number; selected: boolean; disabled: boolean | Replaces option content. Listbox keeps its selection mark and radio or checkbox action. index follows the displayed options. |
Template contexts
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
| Name | Type | Default | Binding | Description |
|---|---|---|---|---|
| label | string | Required | Input | Human-readable name for the control, action or group. |
| options | readonly NeuOption[] | [] | Input | Available choices. Selected values refer to option IDs, not their displayed labels. |
| appearance | "cards" | "plain" | 'cards' | Input | Option 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. |
| itemTemplate | TemplateRef<NeuListboxItemContext> | undefined | undefined | Input | Reusable 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. |
| multiple | boolean | false | Input | Allows multiple choices or files where supported; check the declared model type. |
| 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. |
| emptyLabel | string | 'No options' | Input | Text or accessible name used for empty state. It does not perform the action by itself. |
Models
| Name | Type | Default | Binding | Description |
|---|---|---|---|---|
| value | readonly string[] | [] | Model · two-way | Current edited value or selected ID(s), using the declared type. Bind a writable signal with [(value)]. |
Outputs
| Name | Payload | When it emits |
|---|---|---|
| valueChange | readonly string[] | Emits the updated value value. [(value)] binds this output together with its model input. |
| selected | string | Emits the selected item ID. |
| 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 value = signal<readonly 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. |
NeuListboxItemContext
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;
}| Field | Type | Requirement | Meaning |
|---|---|---|---|
| $implicit | NeuOption | Required | Original item supplied to the row template; available as let-item. |
| index | number | Required | Zero-based position in the displayed collection; available as let-index="index". The personalization section describes how this component handles filtering, sorting or recycled rows. |
| selected | boolean | Required | Whether this option is selected in its template context. |
| disabled | boolean | Required | Prevents activating or selecting this item. |