Quarter Picker
import { QuarterPicker } from '@absoluteui/components/quarter-picker';import '@absoluteui/components/quarter-picker/style.css';Using Angular? See Quarter Picker for Angular.
What it does
Section titled “What it does”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.
When to use
Section titled “When to use”- 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).
When not to use
Section titled “When not to use”- 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.
Examples
Section titled “Examples”Required quarter with bounds
Section titled “Required quarter with bounds”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' });Global quarter picker defaults
Section titled “Global quarter picker defaults”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) : ''; });}More usage
Section titled “More usage”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) });Anatomy
Section titled “Anatomy”<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.
API reference
Section titled “API reference”Factory
Section titled “Factory”QuarterPicker.create(host: HTMLElement, options: QuarterPickerOptions): QuarterPickerGlobal configuration key: quarterPicker (see Configuration).
Options
Section titled “Options”| 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. |
Properties
Section titled “Properties”| 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). |
Methods
Section titled “Methods”| 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. |
Events
Section titled “Events”| 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. |
CSS classes
Section titled “CSS classes”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). |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
calendar |
calendar |
previous |
chevron-left |
next |
chevron-right |
error |
alert-circle |
Design tokens
Section titled “Design tokens”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
TypeScript definitions
Section titled “TypeScript definitions”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 }Accessibility
Section titled “Accessibility”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.
Keyboard
Section titled “Keyboard”| 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) |
ARIA attributes
Section titled “ARIA attributes”| 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.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- 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.
Behavior
Section titled “Behavior”States
Section titled “States”empty, segment focused, partial (incomplete), valid, invalid (message shown), open, cell focused, cell selected, current quarter, cell disabled, disabled, read-only, required.
State transitions
Section titled “State transitions”| 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 |
Accessibility notes
Section titled “Accessibility notes”- 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
isQuarterDisabledgets 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).
Responsive behavior
Section titled “Responsive behavior”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.
Right-to-left
Section titled “Right-to-left”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.
Theming
Section titled “Theming”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.
Global configuration
Section titled “Global configuration”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.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic (update()); there are no init-only options.
Timezones
Section titled “Timezones”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).
Security
Section titled “Security”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.
Data handling
Section titled “Data handling”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.
Performance
Section titled “Performance”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.
Supported data scale
Section titled “Supported data scale”100 pickers per page (performance test); years 1 to 9999.
Edge cases
Section titled “Edge cases”- Typing
3in the quarter segment advances to the year;5reportsinvalid(“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
valuemessage, for example “Enter Q1 2026 or later.”. - Built-in errors set
valuetonull; acustomerror keeps the value. - A registered
patternsuch as{quarter}e trimestre {year}changes the segment order and literals for that locale. update({ value })changes the value but not the value restored byreset()(thevaluepassed tocreate()).- Inside a modal Dialog, the first Escape closes the popup and the second closes the dialog.
Errors
Section titled “Errors”| 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.
Known limitations
Section titled “Known limitations”- 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
patternmessage, not fromIntl; 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.
Unsupported scenarios
Section titled “Unsupported scenarios”- Fiscal quarters or custom quarter-to-month mappings.
- HTML in the label, help text, cells, or messages.
- Custom cell content or grid layouts.