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

tngSelect

The root directive owns the committed single 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 selection semantics intact.

Root attachment

html
<section
  tngSelect
  [value]="selectedStage()"
  (valueChange)="onSelectedStageChange($event)"
>
  <button type="button" tngSelectTrigger>
    <span tngSelectValue>{{ selectedStageLabel() ?? 'Choose workflow stage' }}</span>
    <span tngSelectIcon aria-hidden="true">▾</span>
  </button>

  <div tngSelectContent>
    <div tngSelectOverlay>
      <div
        tngSelectListbox
        [value]="selectedStage()"
        (valueChange)="onSelectedStageChange($event)"
      >
        @for (stage of workflowStages; track stage.value) {
          <div tngSelectOption [tngValue]="stage.value">{{ stage.label }}</div>
        }
      </div>
    </div>
  </div>
</section>
EntryTypeDetails
tngSelectroot directiveOwns open state, committed single value, disabled/loading/invalid flags, and ids used by the trigger + content pair.
value / valueChangestring | nullRepresents the committed option value. Headless usage usually normalizes the primitive payload back to a single string.
open / openChangebooleanControls whether the menu is visible. Pointer and keyboard activation update this automatically unless you override it.
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 text or your richer owned markup inside the trigger.
tngSelectIcondisplay slotOptional icon slot. Use it 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.
tngSelectListbox + tngSelectOptionlistbox bridgeConnects the select root to listbox active/selected state and exposes option data attributes for styling.

Listbox bridge

The select root is single-select, but the listbox bridge still uses the listbox value contract. Normalizing the primitive payload back to a single string keeps the owned headless model predictable.

Single-value normalization

ts
type SelectboxValue = string | readonly string[] | null;

readonly selectedStage = signal<string | null>('review');

onSelectedStageChange(value: SelectboxValue): void {
  this.selectedStage.set(this.toSingleValue(value));
}

private toSingleValue(value: SelectboxValue): string | null {
  if (typeof value === 'string') {
    return value;
  }

  if (Array.isArray(value)) {
    const first = value[0];
    return typeof first === 'string' ? first : null;
  }

  return null;
}
BindingMeaningWhy it matters
[value] on tngSelectcommitted modelThe root stores the committed single value that should show in the trigger when the overlay closes.
[value] on tngSelectListboxselection mirrorKeeps the active/selected option in sync with the root when the listbox renders or the root changes.
(valueChange) on bothsafe headless patternBinding both ends keeps pointer, keyboard, and programmatic updates aligned with the committed single value.

Reflected attributes

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

Reflected state and slots

html
<section tngSelect data-slot="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">QA ready</span>
    <span tngSelectIcon data-slot="select-icon">▾</span>
  </button>

  <div tngSelectContent data-slot="select-content">
    <div tngSelectOverlay>
      <div tngSelectListbox data-slot="select-listbox">
        <div tngSelectOption data-slot="select-option" data-active data-selected>
          QA ready
        </div>
      </div>
    </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.