DateRange
Use CDateRange when users choose a start and end date together. It renders two usable native Date inputs first, then enhances them into one localized Popover and Calendar when JavaScript activates.
Choose one range
Use a fieldset and legend because DateRange submits two controls. It rejects composition inside CField rather than giving two Form fields one field-owned identity.
<fieldset>
<legend>Travel dates</legend>
<c-CDateRange start_name="arrival" end_name="departure" />
</fieldset>
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BasicDateRange(Component):
template = """
<fieldset>
<legend>Travel dates</legend>
<c-CDateRange start_name="arrival" end_name="departure" start="2026-08-19" end="2026-08-23" />
</fieldset>
"""
preview = BasicDateRange()
preview # noqa: B018
The first Calendar selection starts a draft and the second commits an ordered range. Selecting the same date twice commits a one-day range. Hover and focus preview a draft without changing submitted values.
Submit and reset both endpoints
start_name and end_name create separate canonical YYYY-MM-DD FormData entries. Both endpoints are empty or both are committed. Native input and change events fire only after an uncontrolled range commit.
Show code
# ruff: noqa: E501 - embedded example markup stays readable as authored
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DateRangeForm(Component):
template = """
<form x-data="{result:'Submit the form to inspect its canonical values.'}" @submit.prevent="result=JSON.stringify(Object.fromEntries(new FormData($event.target)))">
<fieldset><legend>Conference stay</legend><c-CDateRange start_name="check_in" end_name="check_out" start="2026-09-14" end="2026-09-18" required /></fieldset>
<div><button type="submit">Submit dates</button> <button type="reset">Reset dates</button></div>
<output x-text="result">Submit the form to inspect its canonical values.</output>
</form>
"""
css = ":where(form,fieldset){display:grid;gap:.75rem;max-inline-size:32rem}"
preview = DateRangeForm()
preview # noqa: B018
Without JavaScript the two labeled native Date inputs remain fully usable. Reset restores both initial endpoints and closes the enhanced surface.
Bound the whole interval
min and max are inclusive. unavailable_dates accepts at most 4096 unique dates, and a committed range may not cross any unavailable date. Recheck the same business rules on the server when processing a submission.
Show code
# ruff: noqa: E501 - embedded example markup stays readable as authored
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ConstrainedDateRange(Component):
template = """
<fieldset>
<legend>Available booking window</legend>
<p id="range-help">August 20 and 24 are unavailable; a range cannot cross either date.</p>
<c-CDateRange min="2026-08-10" max="2026-09-15" c-unavailable_dates="('2026-08-20','2026-08-24')" c-attrs="{'aria-describedby':'range-help'}" />
</fieldset>
"""
preview = ConstrainedDateRange()
preview # noqa: B018
Control value and popup visibility
Client value is either {start, end} or null. value and open are independent controlled channels; while supplied, requests call onValueChange or onOpenChange and wait for the owner to return accepted state through $c-props.
Show code
# ruff: noqa: E501 - embedded example markup stays readable as authored
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledDateRange(Component):
template = """
<section x-data="{value:{start:'2026-08-19',end:'2026-08-23'},open:false,last:'No request yet'}">
<p>Range: <strong x-text="value ? `${value.start} through ${value.end}` : 'empty'"></strong>; popup: <strong x-text="open ? 'open' : 'closed'"></strong></p>
<c-CDateRange start="2026-08-19" end="2026-08-23" $c-props="{value,open,onValueChange:(next,detail)=>{last=`${detail.source}: ${JSON.stringify(next)}`;value=next},onOpenChange:(next,detail)=>{last=`${detail.reason}: ${next}`;open=next}}" />
<div><button type="button" @click="value={start:'2026-08-25',end:'2026-08-29'}">Set August 25 through 29</button> <button type="button" @click="open=!open">Toggle popup</button></div>
<output x-text="last">No request yet</output>
</section>
"""
css = ":where(section){display:grid;gap:.75rem;max-inline-size:32rem}:where(section p){margin:0}"
preview = ControlledDateRange()
preview # noqa: B018
Omitting a client prop releases only that channel at its latest committed state.
Follow the active locale
The visible range, Calendar labels, endpoint descriptions, trigger name, and validation text follow the active i18n provider. The canonical Form values do not change when the locale changes.
Show code
# ruff: noqa: E501 - embedded example markup and CSS stay readable as authored
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DateRangeLocales(Component):
template = """
<section class="date-range-locales">
<article><h3>Provider locale week</h3><c-CDateRange start="2026-08-19" end="2026-08-23" /></article>
<article><h3>Explicit Monday start</h3><c-CDateRange start="2026-08-19" end="2026-08-23" c-first_day_of_week="1" /></article>
<article lang="ar" dir="rtl"><h3>RTL scope</h3><c-CDateRange start="2026-08-19" end="2026-08-23" /></article>
</section>
"""
css = ":where(.date-range-locales){display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1rem}:where(.date-range-locales article){display:grid;align-content:start;gap:.5rem;padding:.75rem}:where(.date-range-locales h3){margin:0}"
preview = DateRangeLocales()
preview # noqa: B018
first_day_of_week overrides only the week start. Generic browser parsing of localized date text is not part of this family; its transport always uses native canonical Date controls.
Compare states and presentation
Readonly keeps the range submitted and lets users inspect the Calendar but blocks commits. Disabled blocks interaction and Form participation. Required ranges cannot be cleared.
Show code
# ruff: noqa: E501 - embedded example markup and CSS stay readable as authored
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DateRangeStates(Component):
template = """
<section class="date-range-states">
<fieldset><legend>Optional</legend><c-CDateRange start="2026-08-19" end="2026-08-23" clearable /></fieldset>
<fieldset><legend>Readonly</legend><c-CDateRange start="2026-08-19" end="2026-08-23" readonly variant="filled" size="sm" /></fieldset>
<fieldset><legend>Disabled</legend><c-CDateRange start="2026-08-19" end="2026-08-23" disabled /></fieldset>
<fieldset><legend>Invalid</legend><c-CDateRange start="2026-08-19" end="2026-08-23" invalid variant="plain" size="lg" /></fieldset>
</section>
"""
css = ":where(.date-range-states){display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem}:where(.date-range-states fieldset){min-inline-size:0}"
preview = DateRangeStates()
preview # noqa: B018
Use variant, size, the documented --cui-date-range-* variables, and stable data-citry-ui-part selectors for styling. Nested Calendar and Popover variables keep their own public contracts.
The Calendar uses its complete grid keyboard model. Manual screen-reader review remains important for range grids, especially on mobile.
API reference
Inputs
CDateRange server inputs
Server inputs are passed in a template through <c-CDateRange ... /> or in Python through CDateRange(...).
| Input | Type | Default | Effect |
|---|---|---|---|
start | CDateRangeDate | None (CDateRangeDate) | None | Sets the initial and reset canonical start date or empty range. |
end | CDateRangeDate | None (CDateRangeDate) | None | Sets the initial and reset canonical end date or empty range. |
start_name | str | None | None | Sets the start native transport Form field name. |
end_name | str | None | None | Sets the end native transport Form field name and must differ from start_name. |
form | str | None | None | Associates both native transports with an external Form ID. |
id | str | None | generated | Sets the enhanced Button ID and owned ID prefix. |
min | CDateRangeDate | None (CDateRangeDate) | None | Sets the inclusive minimum endpoint. |
max | CDateRangeDate | None (CDateRangeDate) | None | Sets the inclusive maximum endpoint. |
unavailable_dates | Sequence[CDateRangeDate] | () | Rejects any range crossing one of at most 4096 unique dates. |
required | bool | None | None | Requires both endpoint transports. |
disabled | bool | None | None | Blocks opening selection clearing and Form participation; Form disabledness also wins. |
readonly | bool | None | None | Keeps the range focusable and submitted but blocks commits. |
invalid | bool | None | None | Adds application invalid state to revealed native validity. |
clearable | bool | True | Shows a clear action for an optional non-empty writable range. |
dismissible | bool | True | Permits Escape outside and focus-outside close requests. |
placement | CPopoverPlacement (CPopoverPlacement) | "bottom-start" | Sets the preferred logical Popover placement. |
match_width | bool | True | Makes the Popover at least as wide as the visible control. |
first_day_of_week | Literal[1, 2, 3, 4, 5, 6, 7] | None | None | Overrides locale week start using ISO Monday 1 through Sunday 7. |
show_adjacent_days | bool | True | Shows selectable neighboring-month dates in the Calendar. |
fixed_weeks | bool | True | Uses six stable Calendar rows instead of the natural month row count. |
placeholder | str | "Choose dates" | Supplies visible empty-state text when explicitly overridden. |
range_label | str | "Choose date range" | Names the group popup and empty trigger when explicitly overridden. |
change_label | str | "Change date range, {start} to {end}" | Formats a committed trigger name and must retain both placeholders when explicitly overridden. |
start_label | str | "Start date" | Names the native start input and Calendar start endpoint when explicitly overridden. |
end_label | str | "End date" | Names the native end input and Calendar end endpoint when explicitly overridden. |
clear_label | str | "Clear date range" | Names the clear Button when explicitly overridden. |
unavailable_message | str | "Choose an available date range." | Supplies native custom validity if a committed interval becomes unavailable. |
variant | CDateRangeVariant (CDateRangeVariant) | "outline" | Selects outline filled or plain field treatment. |
size | CDateRangeSize (CDateRangeSize) | "md" | Selects coordinated control and text sizing. |
class_ | CClassValue | None (CClassValue) | None | Adds classes to the root and merges with attrs. |
style | CStyleValue | None (CStyleValue) | None | Adds styles to the root and merges with attrs. |
attrs | Mapping[str, object] | None | None | Adds copied allowed root attributes without replacing owned state identity or runtime markers. |
CDateRange client inputs
Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CDateRange />.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
value | CDateRangeValue | null (CDateRangeValue) | Releases control at the latest committed range. | Controls both ordered selected and submitted endpoints while supplied. |
open | boolean | null | Releases control at the latest committed visibility. | Controls popup visibility while supplied. |
required | boolean | Uses server state. | Controls two-endpoint native required validity. |
disabled | boolean | Uses server or Form state. | Controls interaction and Form participation. |
readonly | boolean | Uses server or Form state. | Controls focusable nonmutable state. |
invalid | boolean | Uses server state. | Controls application invalid state. |
clearable | boolean | Uses the server input. | Controls the optional clear action. |
dismissible | boolean | Uses the server input. | Controls passive popup dismissal. |
placement | CPopoverPlacement (CPopoverPlacement) | Uses the server input. | Controls preferred logical placement. |
matchWidth | boolean | Uses the server input. | Controls trigger-width matching. |
variant | CDateRangeVariant (CDateRangeVariant) | Uses the server input. | Controls field presentation. |
size | CDateRangeSize (CDateRangeSize) | Uses the server input. | Controls coordinated sizing. |
onValueChange | function | No semantic range callback. | Receives Calendar clear native and reset range requests. |
onOpenChange | function | No semantic visibility callback. | Receives trigger selection dismissal reset and forced-close requests. |
Slots
-
Events
Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.
CDateRange events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
onValueChange | (value: CDateRangeValue | null, detail: CDateRangeValueChangeDetail) => void (CDateRangeValue, CDateRangeValueChangeDetail) | A completed Calendar pair clear reset or valid native pair requests another range. | {value, previousValue, controlled, source, sourceEvent} (CDateRangeValueChangeDetail) | Uncontrolled commits emit native input/change for endpoints that changed; controlled requests wait for the owner. |
onOpenChange | (open: boolean, detail: CDateRangeOpenChangeDetail) => void (CDateRangeOpenChangeDetail) | Trigger selection clear reset Escape outside focus-outside native or forced layer changes request visibility. | {reason, controlled, forced, source} (CDateRangeOpenChangeDetail) | Uncontrolled requests commit before notification; controlled requests wait except forced safety closure. |
Methods
-
CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
CDateRange CSS variables
Apply these variables to CDateRange or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
--cui-date-range-background | color | Visible control clear and fallback input background. | Canvas or variant-derived. |
--cui-date-range-foreground | color | Text and icon color. | CanvasText |
--cui-date-range-border-color | color | Control clear and native endpoint boundaries. | Mixed CanvasText. |
--cui-date-range-invalid-border-color | color | Revealed invalid control boundary. | Theme error. |
--cui-date-range-focus-color | color | Focus outlines and draft-range indication. | Highlight |
--cui-date-range-range-background | color | Committed and preview interval background. | Highlight mixed with Canvas. |
--cui-date-range-endpoint-background | color | Start and end date background. | Theme primary. |
--cui-date-range-endpoint-foreground | color | Start and end date foreground. | white |
--cui-date-range-radius | length | Control clear and native input corner radius. | 0.625rem |
--cui-date-range-min-block-size | length | Minimum interactive control height. | 2.5rem |
--cui-date-range-padding-inline | length | Visible control and native endpoint inline inset. | 0.75rem |
--cui-date-range-gap | length | Endpoint field and control content gap. | 0.5rem |
Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.
CDateRange attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
data-empty | Root | present | absent | Marks no committed pair. |
data-open | Root | present | absent | Mirrors effective popup visibility. |
data-required | Root | present | absent | Mirrors effective requiredness. |
data-disabled | Root | present | absent | Mirrors effective disabledness. |
data-readonly | Root | present | absent | Mirrors effective readonly state. |
data-invalid | Root | present | absent | Mirrors application unavailable or revealed native invalidity. |
data-variant | Root | CDateRangeVariant (CDateRangeVariant) | Mirrors visual treatment. |
data-size | Root | CDateRangeSize (CDateRangeSize) | Mirrors coordinated sizing. |
data-enhanced | Root | present | absent | Marks completed custom control activation. |
data-in-range | Calendar day | present | absent | Marks selectable days within the committed or preview interval. |
data-range-start | Calendar day | present | absent | Marks the displayed interval start. |
data-range-end | Calendar day | present | absent | Marks the displayed interval end. |
data-range-preview | Calendar day | present | absent | Marks draft interval days before the second commit. |
Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.
CDateRange selectors
| Selector | Element | Purpose |
|---|---|---|
[data-citry-ui-part="date-range"] | Root div | State reflections root attrs and styling destination. |
[data-citry-ui-part="fallback-group"] | Div | Contains the two no-JavaScript and Form transport fields. |
[data-citry-ui-part="start-input"] | Native Date input | Start endpoint transport validity and reset owner. |
[data-citry-ui-part="end-input"] | Native Date input | End endpoint transport validity and reset owner. |
[data-citry-ui-part="enhanced-control"] | Layout div | Groups the Popover activator and optional clear action. |
[data-citry-ui-part="control"] | Native Button | Full-width popup activator and enhanced public focus target. |
[data-citry-ui-part="value"] | Span | Displays the localized committed range or placeholder. |
[data-citry-ui-part="calendar"] | CCalendar root | Owns draft preview endpoint labels and final range selection. |
[data-citry-ui-part="clear"] | Native Button | Requests an empty optional range. |
Interfaces
Aliases and data shapes referenced above.
Input type aliases
| Interface | Definition |
|---|---|
CDateRangeDate | date | str |
CDateRangeVariant | Literal["outline", "filled", "plain"] |
CDateRangeSize | Literal["sm", "md", "lg"] |
CDateRangeValueChangeSource | Literal["calendar", "clear", "reset", "native"] |
CPopoverPlacement | Literal["top-start", "top", "top-end", "bottom-start", "bottom", "bottom-end"] |
CClassValue | str | Mapping[str, bool] | Sequence[CClassValue] |
CStyleValue | str | Mapping[str, object] | Sequence[CStyleValue] |
CDateRangeValue
| Field | Type | Default | Meaning |
|---|---|---|---|
start | canonical string | - | Inclusive ordered start endpoint. |
end | canonical string | - | Inclusive ordered end endpoint. |
CDateRangeValueChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
value | CDateRangeValue | null (CDateRangeValue) | - | Requested ordered range or empty state. |
previousValue | CDateRangeValue | null (CDateRangeValue) | - | Effective range before the request. |
controlled | boolean | - | Whether client value owns the commit. |
source | CDateRangeValueChangeSource (CDateRangeValueChangeSource) | - | Calendar clear reset or native cause. |
sourceEvent | object | null | - | Native interaction event when one exists. |
CDateRangeOpenChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
reason | trigger | selection | clear | reset | escape | outside | focus-outside | native | ancestor | modal | - | Exact request or forced-close cause. |
controlled | boolean | - | Whether client open owns ordinary visibility commits. |
forced | boolean | - | Whether ancestor or modal safety required closure. |
source | object | null | - | Associated browser source when one exists. |
Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override takes precedence over the catalog for that instance.
CDateRange translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
citry-ui-date-range-placeholder | Displays the empty enhanced control value. | none | placeholder | Parent i18n subscription calls tr() because the destination later displays formatted dates. |
citry-ui-date-range-label | Names the group popup Calendar and empty trigger. | none | range_label or root attrs accessible name | $c-tr updates stable HTML destinations; parent state forwards it into the composed Calendar and dynamic trigger. |
citry-ui-date-range-change | Names a trigger with a committed range. | `start: str` and `end: str` localized by `citry-ui-date-picker-display` | change_label containing {start} and {end} | Parent i18n subscription recomputes both formatted endpoints and calls tr(). |
citry-ui-date-range-start-label | Names the native start input and Calendar start endpoint. | none | start_label | $c-tr updates stable native text; parent state forwards it into Calendar endpoint presentation. |
citry-ui-date-range-end-label | Names the native end input and Calendar end endpoint. | none | end_label | $c-tr updates stable native text; parent state forwards it into Calendar endpoint presentation. |
citry-ui-date-range-clear | Names the optional clear Button. | none | clear_label | $c-tr updates the stable aria-label destination. |
citry-ui-date-range-unavailable | Supplies native custom validity when the committed interval becomes unavailable. | none | unavailable_message | i18n.bind() updates the browser-owned validity message. |