Skip to content

Quarter Picker

Form Inputs / Date and TimeSince 0.3.0
import { QuarterPicker } from '@absoluteui/components/quarter-picker';
import '@absoluteui/components/quarter-picker/style.css';

Using Angular? See Quarter Picker for Angular.

Entry of a calendar quarter. QuarterPicker renders quarter and year segments, ordered by a localizable pattern message (English Q{quarter} {year}), inside a labelled group, plus a “Choose quarter” button that opens a non-modal dialog popup with one row of the four quarters of a year (paged by year); each cell shows its month range. The value is a PlainYearQuarter ({ kind: 'PlainYearQuarter', year, quarter }, created with plainYearQuarter(year, quarter)); Q1 is January to March. A hidden input submits YYYY-Qn. It shares its engine with Month and Year Picker; this page states every behavior in full.

  • A calendar quarter: quarterly reports, targets, filings, or reviews.
  • Users should be able to type a quarter quickly and also see which months each quarter covers.
  • Quarter fields with bounds or unavailable quarters (for example quarters not yet closed).
  • Fiscal quarters of a year that does not start in January: the value and month hints are calendar quarters.
  • A month or a specific day: use Month Picker, Date Picker, or Date Input.
  • A whole year: use Year Picker.
  • A range of quarters: use two Quarter Pickers and a cross-field rule.
import { plainYearQuarter } from '@absoluteui/core';
import { QuarterPicker } from '@absoluteui/components/quarter-picker';
const host = document.createElement('div');
document.body.append(host);
const picker = QuarterPicker.create(host, {
label: 'Reporting quarter',
name: 'quarter',
required: true,
min: plainYearQuarter(2024, 1),
max: plainYearQuarter(2026, 4),
});
host.addEventListener('aui-change', () => {
host.dataset.valid = String(picker.validity.valid);
});

Unavailable quarters and the change detail

Section titled “Unavailable quarters and the change detail”
import { plainYearQuarter, plainYearQuarterToString } from '@absoluteui/core';
import { QuarterPicker, type QuarterPickerChangeDetail } from '@absoluteui/components/quarter-picker';
const host = document.createElement('div');
document.body.append(host);
const lastClosed = plainYearQuarter(2026, 2);
QuarterPicker.create(host, {
label: 'Closed quarter',
isQuarterDisabled: (q) => q.year * 4 + q.quarter > lastClosed.year * 4 + lastClosed.quarter,
});
host.addEventListener('aui-change', (event) => {
const { value } = (event as CustomEvent<QuarterPickerChangeDetail>).detail;
host.dataset.quarter = value ? plainYearQuarterToString(value) : '';
});

Localized segment order through registered messages

Section titled “Localized segment order through registered messages”
import { registerMessages } from '@absoluteui/core';
import { QuarterPicker } from '@absoluteui/components/quarter-picker';
registerMessages('quarterPicker', 'fr', { pattern: '{quarter}e trimestre {year}', value: '{quarter}e trimestre {year}' });
const host = document.createElement('div');
document.body.append(host);
QuarterPicker.create(host, { label: 'Trimestre', locale: 'fr-FR' });
import { configureAbsoluteUI } from '@absoluteui/core';
import '@absoluteui/components/quarter-picker';
configureAbsoluteUI({ common: { timezone: 'Asia/Tokyo' }, components: { quarterPicker: { validationTiming: 'submit' } } });
import { plainYearQuarterToString } from '@absoluteui/core';
import { QuarterPicker, type QuarterPickerChangeDetail } from '@absoluteui/components/quarter-picker';
import '@absoluteui/components/quarter-picker/style.css';
const host = document.querySelector<HTMLElement>('#review');
if (host) {
QuarterPicker.create(host, { label: 'Review quarter', name: 'review' });
host.addEventListener('aui-change', (event) => {
const { value } = (event as CustomEvent<QuarterPickerChangeDetail>).detail;
host.dataset.quarter = value ? plainYearQuarterToString(value) : '';
});
}
import { plainYearQuarter } from '@absoluteui/core';
import { QuarterPicker } from '@absoluteui/components/quarter-picker';
const host = document.createElement('div');
document.body.append(host);
QuarterPicker.create(host, { label: 'Reporting quarter', name: 'quarter', required: true, min: plainYearQuarter(2024, 1) });
<div class="aui-field aui-period-picker aui-quarter-picker" data-aui-control> (consumer host; registered with Form)
<label class="aui-field__label">Label<span class="aui-field__required" aria-hidden="true">*</span></label>
<div class="aui-quarter-picker__group aui-period-picker__group" role="group" aria-labelledby=label aria-describedby>
<div class="aui-period-picker__fields"> (LTR-isolated run)
<span class="aui-period-picker__separator" aria-hidden="true">Q</span>
<input class="aui-period-picker__segment aui-period-picker__segment--quarter" aria-label="Quarter" placeholder="q">
<span class="aui-period-picker__separator" aria-hidden="true"> </span>
<input class="aui-period-picker__segment aui-period-picker__segment--year" aria-label="Year" placeholder="yyyy">
</div>
<button class="aui-period-picker__button" aria-haspopup="dialog" aria-expanded aria-controls=popup aria-label="Choose quarter">
</div>
<div class="aui-field__description"> (hidden when empty)
<div class="aui-field__error aui-validation-message"> (hidden when no message is shown)
<input type="hidden" name>
<div class="aui-period-picker__popup" role="dialog" aria-modal="false" aria-label="Choose quarter" popover="manual">
<div class="aui-period-picker__header">
<button class="aui-period-picker__nav aui-period-picker__prev" aria-label="Previous year">
<div class="aui-period-picker__heading" aria-live="polite">2026</div>
<button class="aui-period-picker__nav aui-period-picker__next" aria-label="Next year">
</div>
<div class="aui-period-picker__grid aui-quarter-picker__grid" role="grid" aria-labelledby=heading>
<div class="aui-period-picker__row" role="row"> (1 row of 4)
<div class="aui-period-picker__cell" role="gridcell" aria-label="Q2 2026, April to June" tabindex="-1">
<span class="aui-period-picker__cell-text">Q2</span>
<span class="aui-period-picker__cell-hint">Apr-Jun</span>
</div> ...
</div>
</div>
</div>
</div>

The popup sits inside the host in the DOM (it renders in the top layer), so moving focus between the segments, the button, and the popup does not mark the field touched.

QuarterPicker.create(host: HTMLElement, options: QuarterPickerOptions): QuarterPicker

Global configuration key: quarterPicker (see Configuration).

Option Type Default Description
label string Visible label and accessible name of the group (via aria-labelledby). Must be a non-empty string. Required.
hideLabel boolean false Visually hide the label; it remains the accessible name.
value PlainYearQuarter | null null Value (years 1-9999). The value passed to create() is the value restored by reset().
min PlainYearQuarter | null null Inclusive lower bound (validation and popup). Must not be after max.
max PlainYearQuarter | null null Inclusive upper bound (validation and popup).
name string Form field name; a hidden input carries YYYY-Qn.
description string Help text (text only) referenced by the group’s aria-describedby.
required boolean false An empty entry is invalid (value-missing); segments are required and the required indicator is shown.
disabled boolean false Disables the segments and the button; the popup does not open and the value is not submitted.
readOnly boolean false Focusable and submitted, but not editable; the popup does not open.
validate (value: PlainYearQuarter | null) => string | null Consumer validation after the built-in checks; return a non-empty message to mark the value invalid (error ‘custom’).
isQuarterDisabled (value: PlainYearQuarter) => boolean Return true to disable a quarter: focusable in the popup but not selectable; typed, it is invalid (error ‘unavailable’).
today PlainDate Advanced: fixed today instead of the current date in timezone; it decides the current quarter. For tests, server rendering, and demos with a fixed clock.
icons Partial<QuarterPickerIcons> Icon slot overrides: calendar, previous, next, error.
validationTiming 'blur' | 'input' | 'submit' "blur" When validation messages first appear; afterwards they update live. Globally configurable.
closeOnSelect boolean true Close the popup after a quarter is picked. Globally configurable.
locale string "en-US" Selects the quarterPicker messages (segment pattern, value, cell text and labels), month names in hints, numbers, and messages. A change rebuilds the segments and keeps the entry. Globally configurable. Inherited from common configuration.
timezone string (IANA) "UTC" Zone used only to determine the current quarter (aria-current and default focus). Globally configurable. Inherited from common configuration.
direction 'ltr' | 'rtl' "ltr" Popup and grid direction: grid arrow-key mirroring, chevrons, and popup alignment (applied when the popup opens). Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement Consumer host; its children are owned by the component. Registered with Form.
group HTMLDivElement The labelled role=group with the segments and the button; control boundary and popup anchor.
button HTMLButtonElement Opens the popup (aria-haspopup=dialog).
popup HTMLDivElement role=dialog aria-modal=false popup (popover=manual).
grid HTMLDivElement The role=grid of quarters inside the popup.
value PlainYearQuarter | null Current value; null when empty, incomplete, or failing a built-in check.
validity QuarterPickerValidity { valid, error, message }; recomputed on every change.
isOpen boolean Whether the popup is open.
name string | null Form field name.
label string Label text.
disabled boolean Disabled state.
dirty boolean Value differs from the initial value, or the segments hold an entry that does not parse to a value.
touched boolean Focus has left the whole control (segments, button, and popup) at least once.
options Readonly<QuarterPickerOptions> Current instance options.
focusedValue PlainYearQuarter The quarter holding the roving tab stop in the popup grid.
current PlainYearQuarter The quarter containing today in the configured timezone (or the today option).
Method Description
setValue(value: PlainYearQuarter | null): void Programmatic value (no events); clears an external custom error. Throws QUARTER_PICKER_OPTIONS for a non-PlainYearQuarter value.
update(options: Partial<QuarterPickerOptions>): void Merge, validate, and apply options. Closes an open popup when it becomes disabled or read-only.
open(): void Open the popup and focus the selected quarter (or the current quarter), clamped to min/max. No-op when open, disabled, or read-only.
close(): void Close the popup; focus inside it returns to the button.
reportValidity(): boolean Show messages now (for every validation timing) and return validity.
setCustomError(message: string | null): void External error (server, cross-field rule); null or ‘’ clears it; cleared when the user edits.
getFormValue(): string | null YYYY-Qn, or null when the value is null.
focus(): void Focus the first segment.
reset(): void Close, restore the initial value, and clear dirty/touched/submitted state (no events).
destroy(): void Close, remove rendering, host classes, listeners, form registration, and subscriptions.
Event Detail Bubbles Cancelable Description
aui-change { value: PlainYearQuarter | null; validity: QuarterPickerValidity } Yes No The user changed the value or its validity error (typing, stepping, a separator, or picking a quarter).
aui-open null No No The popup opened.
aui-close null No No The popup closed.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-field aui-period-picker aui-quarter-picker Host.
aui-period-picker--invalid | --disabled | --readonly State modifiers on the host.
aui-field__label | __required | __description | __error Shared field chrome (the error is also aui-validation-message).
aui-quarter-picker__group aui-period-picker__group Labelled group; control boundary, focus indicator, and popup anchor.
aui-period-picker__fields LTR-isolated run of segments and pattern literals.
aui-period-picker__segment | __segment--quarter | __segment--year Segment inputs.
aui-period-picker__separator Pattern literal text such as Q (aria-hidden).
aui-period-picker__button Choose quarter button.
aui-period-picker__popup Top-layer dialog popup.
aui-period-picker__header | __heading | __nav | __prev | __next Popup header: year heading (aria-live) and previous/next year buttons.
aui-period-picker__grid aui-quarter-picker__grid Grid of 4 quarters (1 row of 4).
aui-period-picker__row Grid row.
aui-period-picker__cell | --selected | --current | --disabled | --empty Quarter cell and its states.
aui-period-picker__cell-text | __cell-hint Quarter text (Q2) and its month range hint (Apr-Jun).
Slot Default icon
calendar calendar
previous chevron-left
next chevron-right
error alert-circle

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-on-primary | --aui-color-primary | --aui-color-selected | --aui-color-selected-text | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-height-sm | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-size-xs | --aui-font-weight-bold | --aui-font-weight-medium | --aui-font-weight-semibold | --aui-icon-size-sm | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3

interface PlainYearQuarter { readonly kind: 'PlainYearQuarter'; readonly year: number; readonly quarter: number }
interface QuarterPickerConfig { validationTiming: ValidationTiming; closeOnSelect: boolean }
type ValidationTiming = 'blur' | 'input' | 'submit';
type QuarterPickerError = 'incomplete' | 'invalid' | 'value-missing' | 'range-underflow' | 'range-overflow' | 'unavailable' | 'custom';
interface QuarterPickerValidity { readonly valid: boolean; readonly error: QuarterPickerError | null; readonly message: string }
interface QuarterPickerChangeDetail { value: PlainYearQuarter | null; validity: QuarterPickerValidity }
interface QuarterPickerIcons { calendar: IconRef; previous: IconRef; next: IconRef; error: IconRef }

Pattern: Segmented quarter field in a labelled group with a button that opens a non-modal dialog popup (WAI-ARIA APG date picker dialog pattern, non-modal variant) containing a paged role=grid of quarters with a roving tabindex.

Key Context Action
Digits Quarter segment Enters one digit and advances to the next segment; 0 and 5-9 are invalid. Non-digits are removed.
Digits Year segment Enters up to four digits and advances when full; fewer than four digits is incomplete.
/ - . , : Space Segment (not read-only) Advances to the next segment.
ArrowUp / ArrowDown Segment (not read-only) Quarter increments or decrements with wrap (1-4); the year stops at 1 and 9999. An empty quarter starts at 1 (Up) or 4 (Down); an empty year starts at the current year.
ArrowLeft / ArrowRight Caret at the segment edge, or text selected Previous or next segment in the visual order of the LTR-isolated segment run (not mirrored in RTL).
Backspace Empty segment Moves to the previous segment.
Alt+ArrowDown Segment Opens the popup without changing the segment.
Tab / Shift+Tab Group Moves through the segments, then the button.
Enter / Space Button Opens the popup (or closes it when open); focus moves to the selected quarter, else the current quarter, clamped to min/max.
ArrowRight / ArrowLeft Quarter cell Next / previous quarter (mirrored in RTL), crossing into the adjacent year.
ArrowDown / ArrowUp Quarter cell Same quarter in the next / previous year (the grid has one row of four).
Home / End Quarter cell Q1 / Q4 of the shown year.
PageDown / PageUp Quarter cell Same quarter in the next / previous year.
Shift+PageDown / Shift+PageUp Quarter cell Same quarter 10 years later / earlier.
Enter / Space Quarter cell Picks the quarter unless it is disabled; with closeOnSelect (default) the popup closes and focus returns to the button, otherwise focus stays on the cell. Emits aui-change if the value or validity changed.
Enter / Space Previous / next year button Shows the previous / next year; no-op while aria-disabled.
Escape Popup open Closes the popup without changing the value; focus inside the popup returns to the button. Only the topmost overlay responds, so a surrounding Dialog stays open.
Tab / Shift+Tab Popup Moves between the previous and next year buttons and the focused quarter; focus leaving the popup (other than to the choose button) closes it.
Element Role
segment container group
each segment (input type=text, inputmode=numeric) textbox (native)
choose quarter button button (native), aria-haspopup=dialog
popup dialog (aria-modal=false)
previous / next year buttons button (native)
quarter grid grid
grid row row
quarter cell gridcell
pattern literals and icons none (aria-hidden=true)
Attribute Element Value
aria-labelledby group Label id
aria-describedby group Help text and visible error message ids
aria-label segment Localized segment name (“Quarter”, “Year”)
aria-invalid segment “true” only while an error message is shown
aria-label button “Choose quarter”, or “Change quarter, {value}” (for example “Change quarter, Q2 2026”) when a value is set
aria-haspopup button dialog
aria-expanded button “true” while the popup is open
aria-controls button Popup id
aria-label popup Localized “Choose quarter”
aria-modal popup “false” (non-modal; the page stays reachable)
aria-live heading polite (announces the year when the page changes)
aria-label previous / next buttons “Previous year” / “Next year”
aria-disabled previous / next buttons “true” when the page reaches min/max or year 1/9999 (focus stays on the button)
aria-labelledby grid Heading id
aria-label cell Quarter, year, and months, for example “Q2 2026, April to June”
aria-selected cell “true” on the selected quarter only (absent otherwise)
aria-current cell “date” on the current quarter (configured timezone or today option)
aria-disabled cell “true” for quarters outside min/max or rejected by isQuarterDisabled

Each segment and the button are tab stops. Opening moves focus to one quarter cell (roving tabindex: exactly one cell has tabindex=0). Movement is clamped to min/max and years 1-9999; disabled quarters stay focusable. Picking with closeOnSelect, Escape, and closing through the button return focus to the button; focus is moved before the popup hides, so it never falls to the document body. A pointer press outside the popup and the button closes it. Moving focus between the segments, button, and popup does not mark the field touched.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.1 Use of Color A
1.4.3 Contrast (Minimum) AA
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.1.2 No Keyboard Trap A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
3.2.2 On Input A
3.3.1 Error Identification A
3.3.2 Labels or Instructions A
4.1.2 Name, Role, Value A
4.1.3 Status Messages AA

Automated test results: Accessibility and compliance.

  • The label is a <label> associated through aria-labelledby because a group is not labelable; clicking the label focuses the first segment.
  • The required asterisk is aria-hidden; each segment carries the native required state.
  • Each cell’s accessible name includes its months (for example “Q2 2026, April to June”), matching the visible month hint.
  • The segments remain the primary input; the grid is an alternative, and a typed quarter rejected by isQuarterDisabled gets a localized message so both paths agree.
  • The popup is non-modal: focus is not trapped and the rest of the page stays reachable.
  • Screen-reader behavior (dialog announcement, page changes, cell states) has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Segment arrow navigation follows visual order and is not mirrored in RTL, because segments render as an LTR run.

empty, segment focused, partial (incomplete), valid, invalid (message shown), open, cell focused, cell selected, current quarter, cell disabled, disabled, read-only, required.

From Trigger To
empty user types part of a quarter dirty, incomplete; message shown only with validationTiming: 'input'
incomplete last segment filled valid, or invalid / range-underflow / range-overflow / unavailable / custom; aui-change
closed button, Alt+ArrowDown, or open() open; focus on the selected (else current) quarter; aui-open
open Enter, Space, or click on an enabled quarter value set, aui-change if changed; with closeOnSelect closed, focus on the button, aui-close
open Escape, the button, or close() closed; value kept; focus inside the popup returns to the button; aui-close
open Tab out of the popup, or a pointer press outside the popup and button closed; focus moves on
any focus leaves the host touched; messages shown (blur timing)
any reportValidity() submitted; messages shown for every timing
invalid (custom error) user edits custom error cleared, validity recomputed
any reset() initial value; pristine, untouched, closed
  • Each cell’s accessible name includes its months, matching the visible hint, so the quarter-to-month mapping is not visual only.
  • The segments stay the primary input; the grid is an alternative. A typed quarter rejected by isQuarterDisabled gets a localized message, so both paths agree.
  • The popup is non-modal: focus is not trapped. Escape and outside presses close it through the shared overlay stack, so only the topmost overlay responds. Focus is moved before the popup hides.
  • Errors appear after the whole control loses focus. The label focuses the first segment when clicked; the required asterisk is aria-hidden.
  • Manual screen-reader verification is pending (see Known limitations).

The group hugs its segments and button, up to 100% of the container; segments wrap inside the group in very narrow containers. The popup is positioned under the group (bottom-start), is at most the viewport width minus a gutter, caps its height to the available space (--aui-available-height), and scrolls when taller.

Segment order and literals come from the pattern message. Segments render as an LTR-isolated run in every direction, so ArrowLeft and ArrowRight in segments follow that visual order and are not mirrored. The button sits at the inline end. The popup and grid take direction: grid ArrowLeft/ArrowRight are mirrored, the chevrons are flipped, and the popup aligns to the inline start of the group.

Role tokens only. The focused segment uses color-selected / color-selected-text; the selected quarter uses color-primary / color-on-primary (its hint inherits the color); the current quarter is bold with an inset color-border-strong border; unavailable quarters are muted and struck through. In forced-colors mode the focused segment and the selected quarter use Highlight / HighlightText, the current quarter gets a CanvasText outline, disabled cells and buttons use GrayText, and the popup has a CanvasText border.

configureAbsoluteUI({ common: { timezone: 'Asia/Tokyo' }, components: { quarterPicker: { validationTiming: 'submit' } } });

Changes apply to existing instances immediately; a locale change rebuilds the segments from that locale’s pattern and keeps the entry.

Localize the texts with registerMessages('quarterPicker', locale, { ... }). Keys and English defaults: pattern “Q{quarter} {year}” (segment order and literals; {quarter} and {year} become segments), value “Q{quarter} {year}”, short “Q{quarter}”, months “{start}-{end}”, cellLabel “Q{quarter} {year}, {start} to {end}”, choose “Choose quarter”, change “Change quarter, {value}”, previous “Previous year”, next “Next year”, plus the validation messages in section 29. Keys missing for a locale fall back to English.

Instance options override global and theme defaults. All options are dynamic (update()); there are no init-only options.

Values are PlainYearQuarter and never shift. The configured timezone (default UTC, never the host zone) only decides the current quarter: the cell marked aria-current="date", the quarter focused when the popup opens on an empty field, and the starting year for ArrowUp/ArrowDown in an empty year segment. At 2026-06-30 20:00 UTC the current quarter is 2026-Q2 in UTC and America/New_York, but 2026-Q3 with timezone: 'Asia/Kolkata' or 'Australia/Lord_Howe'. The advanced today option pins today to a fixed PlainDate.

icons.calendar (default calendar), icons.previous / icons.next (defaults chevron-left / chevron-right), and icons.error (default alert-circle).

All text uses textContent and Intl formatting; registered messages are treated as text, never HTML. Validation messages never include the entered value; range messages include only the formatted bounds. CSP-safe: no inline handlers; positioning writes styles through the CSSOM.

No persistence, network access, or telemetry. When name is set, a hidden input carries YYYY-Qn; it is empty while the entry is empty or fails a built-in check, and disabled (not submitted) while the picker is disabled. A read-only picker is submitted.

The popup and its 4 cells are created once per picker; opening and paging only re-render cell text and state. Listeners are delegated; one configuration subscription per instance. Budget: under 5 ms average per QuarterPicker.create across 100 instances in headless Chromium.

100 pickers per page (performance test); years 1 to 9999.

  • Typing 3 in the quarter segment advances to the year; 5 reports invalid (“Enter a valid quarter (1 to 4).”).
  • Only one segment filled reports incomplete (“Enter a complete quarter.”); a year with fewer than four digits reports “Enter the year with 4 digits.”.
  • Range messages format the bound with the value message, for example “Enter Q1 2026 or later.”.
  • Built-in errors set value to null; a custom error keeps the value.
  • A registered pattern such as {quarter}e trimestre {year} changes the segment order and literals for that locale.
  • update({ value }) changes the value but not the value restored by reset() (the value passed to create()).
  • Inside a modal Dialog, the first Escape closes the popup and the second closes the dialog.
Code Category Cause
QUARTER_PICKER_OPTIONS configuration Missing or blank label; value/min/max not a PlainYearQuarter with year 1-9999; min after max; non-function validate or isQuarterDisabled; today not a PlainDate. Details name the property (for example quarterPicker.value).
CFG_INVALID_VALUE configuration Invalid validationTiming, closeOnSelect, locale, timezone, or direction.
Validation (validity.error) validation incomplete, invalid, value-missing, range-underflow, range-overflow, unavailable, custom.

Built-in checks run in that order: empty (value-missing when required), partial (incomplete), four-digit year (incomplete), segment range (invalid), min, max, isQuarterDisabled (unavailable), then validate, then an external custom error. English messages: “This field is required.”, “Enter a complete quarter.”, “Enter the year with 4 digits.”, “Enter a valid quarter (1 to 4).”, “Enter {min} or later.”, “Enter {max} or earlier.”, “This quarter is not available.”. A failed construction leaves nothing behind.

  • Calendar quarters only (Q1 is January to March); a fiscal-year start is not supported.
  • Gregorian calendar only; years 1 to 9999; only ASCII digits are accepted.
  • Segment order comes from the pattern message, not from Intl; locales without registered messages use the English pattern.
  • Single quarters only; no Cancel/OK buttons (picking commits).
  • Segment arrow navigation is not mirrored in RTL.
  • Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Fiscal quarters or custom quarter-to-month mappings.
  • HTML in the label, help text, cells, or messages.
  • Custom cell content or grid layouts.