Getting StartedInstallation and setup guides 5
LayoutWorkflow and structural layout components 7
OverlayModal and floating layer surfaces 3
FeedbackStatus, empty, progress, and loading placeholder patterns 5
FormInput and selection components 17
UtilityGeneral-purpose interface utilities 7
NavigationMenu surfaces and hierarchical actions 7

API reference

<tng-select> wraps the headless select primitive for the common single-select workflow. You pass options and accessors; the wrapper owns the trigger, overlay, and default listbox wiring.

tng-select

Wrapper attachment

html
<tng-select
  [options]="workflowStages"
  [value]="selectedStage()"
  (valueChange)="onSelectedStageChange($event)"
  [getOptionValue]="getWorkflowStageValue"
  [getOptionLabel]="getWorkflowStageLabel"
  [isOptionDisabled]="isWorkflowStageDisabled"
  placeholder="Choose workflow stage"
  [ariaLabel]="'Workflow stage'"
></tng-select>
PropertyTypeDetails
optionsreadonly O[]Full option collection rendered by the wrapper inside the portaled listbox overlay.
value / valueChangeV | null / outputControlled single-select model for the committed option value.
open / openChangeboolean / outputOptional controlled overlay state when a parent needs to observe or drive the menu.
disabled, loading, invalidbooleanForwarded primitive state inputs reflected onto the wrapper host for visuals and interaction guards.
placeholderstringFallback trigger text when no committed value is present.
iconTextstringOverrides the default chevron text in the trigger icon slot when you want a custom glyph.
labelId / descriptionId / errorIdstringForwarded accessibility ids for external labels, helper copy, and error messaging.

Option accessors

The wrapper stays generic by delegating option identity, labels, disabled state, and tracking to small accessor functions.

Common accessors

ts
interface WorkflowStageOption {
  readonly value: string;
  readonly label: string;
  readonly note: string;
  readonly disabled?: boolean;
}

readonly getWorkflowStageValue = (stage: WorkflowStageOption) => stage.value;
readonly getWorkflowStageLabel = (stage: WorkflowStageOption) => stage.label;
readonly isWorkflowStageDisabled = (stage: WorkflowStageOption) => stage.disabled === true;
readonly trackWorkflowStage = (_index: number, stage: WorkflowStageOption) => stage.value;
AccessorTypeDetails
getOptionValue(option: O) => VMaps each option object to the committed selection value stored in the model.
getOptionLabel(option: O) => stringMaps each option object to the text used in the default trigger value and option rows.
isOptionDisabled(option: O) => booleanDisables individual options while keeping them visible in the listbox.
trackBy(index: number, option: O) => unknownCustom identity function for stable option rendering when the input list changes.

Angular Signal Forms

<tng-select> can bind directly with [formField]. The wrapper host exposes a model-backed selection value, so you can keep the committed option inside a signal form without a separate CVA adapter.

Signal forms wiring

ts
import { Component, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { TngSelectComponent } from '@tailng-ui/components';

type ReleaseOwner = {
  readonly id: string;
  readonly label: string;
};

@Component({
  selector: 'app-release-owner-signal-form',
  standalone: true,
  imports: [FormField, TngSelectComponent],
  template: \`
    <tng-select
      [formField]="releaseForm.owner"
      [options]="owners"
      [getOptionValue]="getOwnerValue"
      [getOptionLabel]="getOwnerLabel"
      placeholder="Choose release owner"
      aria-label="Release owner"
    ></tng-select>
  \`,
})
export class ReleaseOwnerSignalFormComponent {
  readonly releaseModel = signal({ owner: 'alex' });
  readonly releaseForm = form(this.releaseModel);

  readonly owners: readonly ReleaseOwner[] = [
    { id: 'alex', label: 'Alex' },
    { id: 'bri', label: 'Bri' },
  ];

  readonly getOwnerValue = (owner: ReleaseOwner) => owner.id;
  readonly getOwnerLabel = (owner: ReleaseOwner) => owner.label;
}

Template hooks

You can replace the trigger value markup and option rows through content templates while the wrapper keeps the primitive selection logic intact.

Template hooks

html
<tng-select
  [options]="releaseOwners"
  [value]="selectedOwner()"
  (valueChange)="onSelectedOwnerChange($event)"
  [getOptionValue]="getOwnerValue"
  [getOptionLabel]="getOwnerLabel"
>
  <ng-template #tngSelectValueTpl let-selected>
    <div>
      <strong>{{ selected.label }}</strong>
      <small>{{ selected.option?.team }}</small>
    </div>
  </ng-template>

  <ng-template #tngSelectOptionTpl let-option>
    <div>
      <strong>{{ option.label }}</strong>
      <small>{{ option.option.team }}</small>
    </div>
  </ng-template>
</tng-select>
TemplateContextDetails
#tngSelectValueTplTemplateRef<{ value, option, label }>Replaces the default trigger value markup while the wrapper keeps trigger semantics and selection state.
#tngSelectOptionTplTemplateRef<{ option, value, label, disabled, selected, active }>Replaces each option row for richer metadata layouts without rebuilding the wrapper shell.

Primitive foundation

The wrapper is intentionally opinionated around the common trigger + menu select experience. Use the headless primitive when you need full trigger or overlay markup control.

CapabilityAvailabilityGuidance
Trigger + overlay shellWrapper-ownedThe wrapper owns the trigger button, icon, portaled overlay, and listbox plumbing for the common select pattern.
Markup ownershipTemplate hooks onlyUse templates for richer content, or drop to headless when you need full trigger or overlay DOM ownership.
Primitive escape hatchAvailableUse headless select when you need custom trigger composition, overlay structure, or direct primitive coordination.