# Date Range

Use `ui-date-range` when users need one continuous period instead of two loosely related dates. It fits booking, availability, campaigns, reporting windows, and travel flows where the start and end date belong to the same interaction.

## Import
```ts
import { DateRangeComponent, type DateRange } from 'ui';
```

## Basic range selection
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, DateRange, DateRangeComponent } from 'ui';

@Component({
  selector: 'app-date-range-basic-demo',
  standalone: true,
  imports: [FormsModule, ButtonComponent, DateRangeComponent],
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;max-width:24rem">
      <ui-date-range
        label="Travel window"
        helpText="Pick the start and end date."
        [(ngModel)]="value"
        [ngModelOptions]="{ standalone: true }"
      />

      <div
        style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
      >
        <ui-button type="button" appearance="subtle" (click)="clear()">Clear</ui-button>
        <span style="font-size:0.75rem;color:var(--color-neutral-foreground2-rest)">
          {{ formatRange(value) }}
        </span>
      </div>
    </div>
  `,
})
export class DateRangeBasicDemoComponent {
  protected value: DateRange | null = {
    startDate: '2026-05-12',
    endDate: '2026-05-18',
  };

  protected clear(): void {
    this.value = null;
  }

  protected formatRange(range: DateRange | null): string {
    if (!range?.startDate && !range?.endDate) {
      return 'No range selected.';
    }

    return `${range?.startDate || '...'} - ${range?.endDate || '...'}`;
  }
}
```

## Separator and range preview
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DateRange, DateRangeComponent } from 'ui';

@Component({
  selector: 'app-date-range-separator-preview-demo',
  standalone: true,
  imports: [FormsModule, DateRangeComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:1rem;align-items:start"
    >
      <ui-date-range
        label="Default separator"
        helpText="Standard range input with hover preview between dates."
        [(ngModel)]="defaultRange"
        [ngModelOptions]="{ standalone: true }"
      />

      <ui-date-range
        label="Custom separator"
        helpText="Useful when the surrounding product language prefers the word to."
        separator=" to "
        [(ngModel)]="customSeparatorRange"
        [ngModelOptions]="{ standalone: true }"
      />

      <ui-date-range
        label="Month jump enabled"
        helpText="Open the picker and use the month or year header to move faster."
        [showMonthYearPicker]="true"
        [(ngModel)]="pickerRange"
        [ngModelOptions]="{ standalone: true }"
      />
    </div>
  `,
})
export class DateRangeSeparatorPreviewDemoComponent {
  protected defaultRange: DateRange | null = {
    startDate: '2026-06-02',
    endDate: '2026-06-06',
  };

  protected customSeparatorRange: DateRange | null = {
    startDate: '2026-06-10',
    endDate: '2026-06-14',
  };

  protected pickerRange: DateRange | null = {
    startDate: '2026-07-01',
    endDate: '2026-07-09',
  };
}
```

## Min and max constraints
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DateRange, DateRangeComponent } from 'ui';

@Component({
  selector: 'app-date-range-constraints-demo',
  standalone: true,
  imports: [FormsModule, DateRangeComponent],
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;max-width:24rem">
      <ui-date-range
        label="Booking window"
        helpText="Only dates inside the allowed window can be selected."
        min="2026-05-12"
        max="2026-05-26"
        [(ngModel)]="value"
        [ngModelOptions]="{ standalone: true }"
      />

      <div
        style="display:flex;flex-direction:column;gap:0.375rem;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest);font-size:0.75rem;color:var(--color-neutral-foreground2-rest)"
      >
        <span>Allowed window: 2026-05-12 to 2026-05-26</span>
        <span>Selected: {{ value?.startDate || '...' }} - {{ value?.endDate || '...' }}</span>
      </div>
    </div>
  `,
})
export class DateRangeConstraintsDemoComponent {
  protected value: DateRange | null = {
    startDate: '2026-05-14',
    endDate: '2026-05-18',
  };
}
```

## Readonly, disabled, and required states
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DateRange, DateRangeComponent } from 'ui';

@Component({
  selector: 'app-date-range-states-demo',
  standalone: true,
  imports: [FormsModule, DateRangeComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:1rem;align-items:start"
    >
      <ui-date-range
        label="Readonly"
        helpText="The range stays visible but cannot be changed."
        [readonly]="true"
        [(ngModel)]="readonlyValue"
        [ngModelOptions]="{ standalone: true }"
      />

      <ui-date-range
        label="Disabled"
        helpText="The field is unavailable."
        [disabled]="true"
        [(ngModel)]="disabledValue"
        [ngModelOptions]="{ standalone: true }"
      />

      <ui-date-range
        label="Required"
        helpText="Choose the review period."
        [required]="true"
        [(ngModel)]="requiredValue"
        [ngModelOptions]="{ standalone: true }"
      />
    </div>
  `,
})
export class DateRangeStatesDemoComponent {
  protected readonlyValue: DateRange | null = {
    startDate: '2026-05-05',
    endDate: '2026-05-09',
  };

  protected disabledValue: DateRange | null = {
    startDate: '2026-05-19',
    endDate: '2026-05-22',
  };

  protected requiredValue: DateRange | null = null;
}
```

## Reactive form integration
```ts
import { Component, computed } from '@angular/core';
import { AbstractControl, FormControl, ReactiveFormsModule } from '@angular/forms';
import { ButtonComponent, DateRange, DateRangeComponent, MessageBarComponent } from 'ui';

@Component({
  selector: 'app-date-range-reactive-form-demo',
  standalone: true,
  imports: [ReactiveFormsModule, ButtonComponent, DateRangeComponent, MessageBarComponent],
  template: `
    <div
      style="display:flex;flex-direction:column;gap:1rem;max-width:24rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
    >
      <div style="display:flex;flex-direction:column;gap:0.25rem">
        <div style="font-size:0.9375rem;font-weight:600">Campaign period</div>
        <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          Date range usually matters as part of a real form flow with validation and submit state.
        </div>
      </div>

      <ui-date-range
        label="Launch period"
        helpText="Choose the start and end date for the campaign."
        [required]="true"
        [formControl]="rangeControl"
      />

      @if (rangeControl.invalid && rangeControl.touched) {
        <ui-message-bar
          title="Range required"
          message="Choose both dates before continuing."
          variant="warning"
          appearance="subtle"
          [dismissible]="false"
        />
      }

      <div
        style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
      >
        <ui-button type="button" variant="primary" [disabled]="rangeControl.invalid">
          Save period
        </ui-button>
        <ui-button type="button" appearance="subtle" (click)="reset()">Reset</ui-button>
        <span style="font-size:0.75rem;color:var(--color-neutral-foreground2-rest)">
          {{ summary() }}
        </span>
      </div>
    </div>
  `,
})
export class DateRangeReactiveFormDemoComponent {
  protected readonly rangeControl = new FormControl<DateRange | null>(
    {
      startDate: '2026-06-01',
      endDate: '2026-06-15',
    },
    { validators: [this.rangeValidator] },
  );

  protected readonly summary = computed(() => {
    const value = this.rangeControl.value;
    if (!value?.startDate && !value?.endDate) {
      return 'No range selected.';
    }

    return `${value?.startDate || '...'} - ${value?.endDate || '...'}`;
  });

  protected reset(): void {
    this.rangeControl.reset({
      startDate: '2026-06-01',
      endDate: '2026-06-15',
    });
  }

  private rangeValidator(control: AbstractControl<DateRange | null>) {
    const value = control.value;
    return value?.startDate && value?.endDate ? null : { required: true };
  }
}
```

## Booking panel composition
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
  ButtonComponent,
  DateRange,
  DateRangeComponent,
  MessageBarComponent,
  TagComponent,
} from 'ui';

@Component({
  selector: 'app-date-range-booking-panel-demo',
  standalone: true,
  imports: [FormsModule, ButtonComponent, DateRangeComponent, MessageBarComponent, TagComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));gap:1rem;align-items:start;max-width:48rem"
    >
      <div
        style="display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="display:flex;flex-direction:column;gap:0.25rem">
          <div style="font-size:0.9375rem;font-weight:600">Stay planning</div>
          <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
            Date range is strongest when users need a clear start and end period with surrounding
            booking context.
          </div>
        </div>

        <ui-date-range
          label="Stay dates"
          helpText="Choose check-in and check-out dates."
          [(ngModel)]="value"
          [ngModelOptions]="{ standalone: true }"
        />

        <ui-message-bar
          title="Availability note"
          message="Confirm the selected stay with the guest before sending the final summary."
          variant="info"
          appearance="subtle"
          [dismissible]="false"
        />
      </div>

      <div
        style="display:flex;flex-direction:column;gap:0.875rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="display:flex;flex-wrap:wrap;gap:0.5rem">
          <ui-tag text="Travel" appearance="filled" variant="secondary" />
          <ui-tag text="Flexible dates" appearance="subtle" variant="info" />
          <ui-tag text="Ready to confirm" appearance="subtle" variant="success" />
        </div>

        <div style="display:flex;flex-direction:column;gap:0.375rem">
          <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
            Selected range
          </div>
          <div style="font-size:0.9375rem;font-weight:600">
            {{ value?.startDate || '...' }} - {{ value?.endDate || '...' }}
          </div>
        </div>

        <div
          style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
        >
          <ui-button type="button" variant="primary">Confirm stay</ui-button>
          <ui-button type="button" appearance="subtle" (click)="value = null">Reset</ui-button>
        </div>
      </div>
    </div>
  `,
})
export class DateRangeBookingPanelDemoComponent {
  protected value: DateRange | null = {
    startDate: '2026-07-08',
    endDate: '2026-07-15',
  };
}
```

## Accessibility

### Input and popup behavior
`ui-date-range` behaves like one combined text field and opens a calendar dialog-style popup for choosing the start and end dates.

| Element | Behavior |
| --- | --- |
| desktop text input | exposes `aria-haspopup="dialog"` and `aria-expanded` |
| disabled or readonly | prevents opening or changing the range |
| clear action | removes both dates from the current value |

### Keyboard
Keyboard handling follows the shared field and popup model.

| Key | Action |
| --- | --- |
| `Tab` / `Shift+Tab` | moves through the field and calendar controls |
| `Enter` / click on trigger actions | opens the range picker panel |
| calendar button activation | selects the start date, then the end date |

### Labels and guidance
Use a task-specific label such as booking dates, launch period, or stay dates so users understand the period context immediately. Add helper text when the range must stay inside a known window or when the start and end dates have a domain-specific meaning.
