Getting StartedInstall and bootstrap headless foundations 4
LayoutHeadless structural primitives for expandable and container patterns 6
OverlayHeadless modal and floating layer behavior 3
FeedbackHeadless notification and status communication patterns 5
FormHeadless input and selection contracts 17
UtilityReusable action, identity, and clipboard behavior 6
NavigationHeadless trails, trees, menus, and command surfaces 7

tngMultiSelect

The root directive owns the committed array value, open state, and the aria wiring that the trigger and content use. Headless usage means you control the DOM around it while the primitive keeps multi-selection semantics intact.

Root attachment

html
<section
  tngMultiSelect
  [value]="selectedPlanets()"
  (valueChange)="onValueChange($event)"
>
  <button type="button" tngSelectTrigger>
    <span tngSelectValue>{{ selectedSummary() }}</span>
    <span tngSelectIcon aria-hidden="true">▾</span>
  </button>

  <div tngSelectContent>
    <div tngSelectOverlay>
      <ul
        tngMultiSelectListbox
        [multiple]="true"
        [value]="selectedPlanets()"
      >
        @for (planet of planets; track planet.value) {
          <li tngMultiSelectOption [tngValue]="planet.value">{{ planet.label }}</li>
        }
      </ul>
    </div>
  </div>
</section>
EntryTypeDetails
tngMultiSelectroot directiveOwns open state, committed array value, disabled/loading/invalid flags, and ids used by the trigger + content pair.
value / valueChangereadonly T[]Represents the committed set of selected option values. Emitted as an array after every toggle.
open / openChangebooleanControls whether the menu is visible. Enter toggles the active option without closing; outside click or Escape closes.
disabled, loading, invalidbooleanReflected onto the root host for semantics and styling, then consumed by the trigger and overlay parts.

Owned parts

Each part directive is small on purpose. You own the markup; the primitive annotates it with slots, state, and interaction behavior.

PartRoleGuidance
tngSelectTriggercombobox triggerFocus target and keyboard entry point. The trigger toggles the menu and owns aria-expanded + aria-controls.
tngSelectValuedisplay slotReceives the committed label summary or your richer owned markup inside the trigger.
tngSelectIcondisplay slotOptional icon slot for chevrons or status glyphs without changing trigger semantics.
tngSelectContent + tngSelectOverlayoverlay shellThe content wrapper tracks hidden state while the overlay carries the portaled menu surface.
tngMultiSelectListbox + tngMultiSelectOptionlistbox bridgeConnects the multiselect root to listbox active/selected state. Options expose data attributes for styling.

Listbox bridge

The multiselect root models values as arrays. Normalize the emitted payload to readonly string[] in your handler.

Array-value normalization

ts
readonly selectedPlanets = signal<readonly string[]>(['earth', 'mars']);

onValueChange(value: unknown): void {
  this.selectedPlanets.set(this.toValueArray(value));
}

private toValueArray(value: unknown): readonly string[] {
  if (value === null || value === undefined) return [];
  if (Array.isArray(value))
    return value.filter((v): v is string => typeof v === 'string');
  return typeof value === 'string' ? [value] : [];
}
BindingMeaningWhy it matters
[value] on tngMultiSelectcommitted modelThe root stores the committed array of values that should show in the trigger summary.
[multiple]="true" on tngMultiSelectListboxmulti-selection modeEnables multi-selection semantics so Enter toggles instead of closing.
(valueChange) on rootsafe headless patternEmits the full updated array after each toggle. Normalize to readonly string[] in your handler.

Reflected attributes

The primitive reflects stable slots and state so you can style the multiselect without reaching for brittle DOM assumptions.

Reflected state and slots

html
<section tngMultiSelect data-slot="multi-select" data-state="open">
  <button
    tngSelectTrigger
    role="combobox"
    aria-expanded="true"
    aria-controls="tng-select-content-..."
    data-slot="select-trigger"
  >
    <span tngSelectValue data-slot="select-value">Earth, Mars</span>
    <span tngSelectIcon data-slot="select-icon">▾</span>
  </button>

  <div tngSelectContent data-slot="select-content">
    <div tngSelectOverlay>
      <ul tngMultiSelectListbox data-slot="multi-select-listbox">
        <li tngMultiSelectOption data-slot="multi-select-option" data-active data-selected>
          Earth
        </li>
      </ul>
    </div>
  </div>
</section>
AttributeScopePurpose
data-slotstructural markerAdded to the root, trigger, value, icon, content, listbox, and option parts for styling hooks.
data-stateroot stateThe root reflects open or closed state so your shell can respond when the menu is visible.
data-active / data-selected / data-disabledoption stateApplied to option rows for hover-equivalent focus, committed selection, and disabled styling.