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

API reference

Headless Datepicker is a controller plus a small set of binding directives. You still own the field, trigger, overlay, and calendar layout, while the primitive owns parsing, roving focus, panel transitions, and the public accessibility attributes.

createDatepickerController(...)

Controller + Angular binding

ts
import { bindTngDatepicker, createDatepickerController } from '@tailng-ui/primitives';

readonly controller = createDatepickerController<Date>({
  ownerDocument: document,
  value: '2024-04-22',
  today: '2024-04-18',
  minDate: '2024-04-01',
  maxDate: '2026-03-31',
  closeOnSelect: true,
  trapFocus: true,
  showOutsideDays: true,
});

readonly datepicker = bindTngDatepicker(this.controller);
OptionPurpose
value / defaultValueInitial or controlled selection state.
todayOverrides the date marked as today in the calendar grid.
minDate / maxDateDisables out-of-range days, months, and years.
adapterControls input parsing, formatting, and visible period labels.
closeOnSelectCloses the popup after a committed day selection.
trapFocusKeeps focus inside the popup while it is open.
showOutsideDaysShows adjacent-month days in the visible month grid.
placement / overlayGapControls overlay positioning and spacing from the field.

Primitive directives

These directives are the current headless binding layer. They keep the docs and applications out of the manual key, click, aria, and active-descendant plumbing that the older examples had to repeat.

Field + overlay wiring

html
<section [tngDatepickerHost]="controller">
  <div data-slot="datepicker-field">
    <div #anchorShell>
      <div
        data-slot="datepicker-input-shell"
        [attr.data-invalid]="datepicker.outputs().validationError !== null ? 'true' : null"
        [attr.data-open]="datepicker.outputs().getTriggerAttributes()['data-open']"
      >
        <input [tngDatepickerInput]="controller" type="text" placeholder="MM-DD-YYYY" />
        <button [tngDatepickerTrigger]="controller" type="button">Open</button>
      </div>

      <section [tngDatepickerOverlay]="controller" [tngDatepickerOverlayAnchor]="anchorShell">
        <button [tngDatepickerPrevButton]="controller" type="button">‹</button>
        <button [tngDatepickerPeriodButton]="controller" type="button">
          {{ datepicker.periodLabel() }}
        </button>
        <button [tngDatepickerNextButton]="controller" type="button">›</button>
      </section>
    </div>
  </div>
</section>
DirectivePurpose
[tngDatepickerHost]Applies the public root attributes like data-open, data-view, and root ARIA labels.
[tngDatepickerInput]Wires input text, manual commits, open-on-click, and basic keyboard entry into the controller.
[tngDatepickerTrigger]Registers the trigger element and forwards the wrapper-grade trigger keyboard behavior.
[tngDatepickerOverlay]Ports the popup to document.body, syncs overlay attrs, and keeps positioning/focus semantics aligned.
[tngDatepickerPrevButton] / [tngDatepickerNextButton]Pages the current view without repeating day/month/year branching in your component code.
[tngDatepickerPeriodButton]Handles the standard period drill-down flow and keeps focus synced after the view changes.
[tngDatepickerDayGrid], [tngDatepickerMonthGrid], [tngDatepickerYearGrid]Forward the correct keyboard behavior for each panel.
[tngDatepickerDayCell], [tngDatepickerMonthOption], [tngDatepickerYearOption]Apply the public cell attrs and click behavior so buttons stay headless but not repetitive.

Grid rendering

The controller still exposes the view data. The difference is that the cell and grid directives now own the common interactions, so the template only needs to render the panels it wants.

This keeps the layout genuinely headless: you decide which panel to show and how to arrange it, while the primitive keeps the keyboard and selection model consistent.

Day / month / year panels

html
@if (datepicker.outputs().view === 'day') {
  <div [tngDatepickerDayGrid]="controller">
    @for (cell of datepicker.outputs().cells; track cell.id) {
      <button [tngDatepickerDayCell]="cell" type="button">{{ cell.label }}</button>
    }
  </div>
}

@if (datepicker.outputs().view === 'month') {
  <div [tngDatepickerMonthGrid]="controller">
    @for (option of datepicker.outputs().monthOptions; track option.id) {
      <button [tngDatepickerMonthOption]="option" type="button">{{ option.label }}</button>
    }
  </div>
}

@if (datepicker.outputs().view === 'year') {
  <div [tngDatepickerYearGrid]="controller">
    @for (option of datepicker.outputs().yearOptions; track option.id) {
      <button [tngDatepickerYearOption]="option" type="button">{{ option.label }}</button>
    }
  </div>
}

Controller outputs

datepicker.outputs() is the primary read model. The get*Attributes() helpers are still available for advanced custom composition, but they are now the lower-level escape hatch rather than the default path.

OutputDetails
cellsVisible day cells with disabled, selected, active, today, and in-month state.
monthOptions / yearOptionsPicker options for month and year drill-down panels.
inputTextCurrent editable input text, including in-progress manual entry.
labelMonthYearReady-to-render month/year label for the day view header.
getHostAttributes() / getOverlayAttributes() / getGridAttributes()Low-level attribute maps for advanced compositions or environments where you are not using the helper directives.
getCellAttributes(...) / getMonthAttributes(...) / getYearAttributes(...)Low-level item attrs for fully custom cell markup beyond the provided option directives.

Controller methods

MethodPurpose
open() / close() / toggleOpen()Owns popup visibility.
setInputText(...) / commitInputText()Supports manual editing with adapter validation and bounds checks.
subscribe(...)Low-level subscription hook. Angular apps can usually prefer bindTngDatepicker(...) instead.
showYearsPanel() / showMonthsPanel() / showDaysPanel()Lets the implementation drive the visible panel explicitly when it wants a custom view flow.
prevMonth() / nextMonth() / prevYear() / nextYear()Pages the visible range for the current view.
selectMonth(...) / selectYear(...) / handleCellClick(...)Low-level selection hooks for layouts that want to bypass the stock option directives.
handleTriggerKeyDown(...) / handleOverlayKeyDown(...) / handleGridKeyDown(...)Still available as the lower-level escape hatch if you are building a custom directive layer of your own.