@antadesign/anta 0.3.17 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +38 -242
  2. package/dist/components/Input.d.ts +4 -1
  3. package/dist/components/Input.js +2 -0
  4. package/dist/components/InputDate.css +1 -0
  5. package/dist/components/InputDate.d.ts +1 -0
  6. package/dist/components/InputDate.js +5 -5
  7. package/dist/components/Loader.d.ts +39 -0
  8. package/dist/components/Loader.js +33 -0
  9. package/dist/components/Menu.d.ts +5 -1
  10. package/dist/components/Menu.js +3 -2
  11. package/dist/components/Progress.d.ts +17 -8
  12. package/dist/components/Progress.js +12 -9
  13. package/dist/components/Select.d.ts +1 -0
  14. package/dist/components/Select.js +9 -13
  15. package/dist/components/SelectFaceted.d.ts +1 -0
  16. package/dist/components/SelectFaceted.js +29 -23
  17. package/dist/components/select-parts.css +1 -0
  18. package/dist/elements/a-button.css +1 -1
  19. package/dist/elements/a-icon.d.ts +1 -0
  20. package/dist/elements/a-icon.js +1 -0
  21. package/dist/elements/a-input.css +1 -1
  22. package/dist/elements/a-loader.css +1 -0
  23. package/dist/elements/a-loader.d.ts +12 -0
  24. package/dist/elements/a-loader.js +15 -0
  25. package/dist/elements/a-menu.js +12 -0
  26. package/dist/elements/a-progress.css +1 -1
  27. package/dist/elements/a-progress.js +32 -2
  28. package/dist/elements/a-slider.css +1 -1
  29. package/dist/elements/a-tabs.css +1 -1
  30. package/dist/elements/a-tabs.d.ts +3 -0
  31. package/dist/elements/a-tabs.js +40 -1
  32. package/dist/elements/index.d.ts +1 -0
  33. package/dist/elements/index.js +3 -0
  34. package/dist/general_types.d.ts +27 -2
  35. package/dist/index.d.ts +6 -1
  36. package/dist/index.js +2 -0
  37. package/dist/jsx-runtime.d.ts +11 -1
  38. package/dist/reset.css +1 -1
  39. package/dist/theme-anta.css +1 -1
  40. package/docs/accessibility.md +34 -0
  41. package/docs/changelog.md +551 -0
  42. package/docs/colors.md +42 -0
  43. package/docs/comparison.md +28 -0
  44. package/docs/components/banner.md +267 -0
  45. package/docs/components/button.md +603 -0
  46. package/docs/components/card.md +322 -0
  47. package/docs/components/checkbox.md +297 -0
  48. package/docs/components/dialog.md +314 -0
  49. package/docs/components/expander.md +529 -0
  50. package/docs/components/icon.md +155 -0
  51. package/docs/components/input-autocomplete.md +101 -0
  52. package/docs/components/input-date.md +406 -0
  53. package/docs/components/input-time.md +174 -0
  54. package/docs/components/input.md +610 -0
  55. package/docs/components/loader.md +139 -0
  56. package/docs/components/menu.md +447 -0
  57. package/docs/components/progress.md +182 -0
  58. package/docs/components/radio.md +369 -0
  59. package/docs/components/select-faceted.md +456 -0
  60. package/docs/components/select.md +931 -0
  61. package/docs/components/slider.md +142 -0
  62. package/docs/components/switch.md +198 -0
  63. package/docs/components/tabs.md +471 -0
  64. package/docs/components/tag.md +185 -0
  65. package/docs/components/text.md +148 -0
  66. package/docs/components/title.md +146 -0
  67. package/docs/components/toaster.md +313 -0
  68. package/docs/components/tooltip.md +177 -0
  69. package/docs/credits.md +22 -0
  70. package/docs/index.md +52 -0
  71. package/docs/install-config.md +240 -0
  72. package/docs/normalization.md +105 -0
  73. package/docs/overview.md +10 -0
  74. package/docs/packages/stickers.md +187 -0
  75. package/docs/packages/table.md +153 -0
  76. package/docs/theming.md +13 -0
  77. package/package.json +10 -6
@@ -0,0 +1,101 @@
1
+ # InputAutocomplete
2
+
3
+ `InputAutocomplete` — a text field whose value is a free string, with a suggestion list that assists
4
+ without constraining. Type anything: a value that isn't in the list stays as you
5
+ typed it, and picking a suggestion fills the field. For a value that must be one of
6
+ the options, use [Select](./select.md) with `filter`.
7
+
8
+ It's a **composed component**, an [Input](./input.md) stitched to a [Menu](./menu.md) of
9
+ suggestions. The field text arrives through **`onValueChange`**; `onSelect` fires
10
+ only when a suggestion is chosen. Controlled via `value`, uncontrolled via
11
+ `defaultValue`.
12
+
13
+ ## Free text is the point
14
+
15
+ The value is always the text in the field. That's what separates this from a
16
+ [Select](./select.md): a Select constrains the value to one of its options, so a Select
17
+ with `filter` already covers "search a fixed list". `InputAutocomplete` is for when
18
+ the list only *suggests* and the user may commit something you didn't list.
19
+
20
+ The keyboard follows that intent. No suggestion is highlighted as you type, so
21
+ Enter commits the typed text and closes the list. ↓ highlights
22
+ the first suggestion (then ↑/↓ move through them), and
23
+ Enter on a highlighted row picks it. Esc or an outside click
24
+ closes the list; clicking the field to place the caret keeps it open.
25
+
26
+ ## Filtering
27
+
28
+ `filter` decides how suggestions match the text:
29
+
30
+ - **`true`** (default): built-in case-insensitive substring match on each option's
31
+ `value` / `label` / `hint`.
32
+ - **a function** `(option, query) => boolean`: your own matcher, called per option.
33
+ - **`false`**: show `suggestions` as-is, doing no local filtering. Use this when you
34
+ fetch or filter results yourself (async / remote) and feed the current set through
35
+ `suggestions`.
36
+
37
+ Suggestions are bare strings or `SelectOption`s, the same option shape
38
+ [Select](./select.md) uses: `value`, `label`, `hint`, `icon`, `tone`, `disabled`.
39
+
40
+ ### Props
41
+
42
+ | Prop | Type | Default | Description |
43
+ |------|------|---------|-------------|
44
+ | `suggestions` | string \| SelectOption[] | — | The suggestions: bare strings or `SelectOption`s (`value` / `label` / `hint` /
45
+ `icon` / `tone` / `disabled`). A flat list; picking one fills the field. |
46
+ | `clearable?` | boolean | — | Show a clear button while the field has text. |
47
+ | `defaultValue?` | string | — | Initial value for the uncontrolled case (the wrapper then owns it). |
48
+ | `disabled?` | boolean | — | Disable the field. |
49
+ | `filter?` | boolean \| (option, query) => boolean | true | How suggestions match the text. `true` uses the built-in case-insensitive
50
+ substring match on `value` / `label` / `hint`; a function `(option, query) =>
51
+ boolean` does custom matching; `false` shows `suggestions` verbatim so you
52
+ can filter them yourself (async / remote). |
53
+ | `hint?` | ReactNode | — | Hint under the field. |
54
+ | `label?` | ReactNode | — | Field label. |
55
+ | `leading?` | ReactNode | — | Leading adornment inside the field. |
56
+ | `name?` | string | — | Form field name. |
57
+ | `onSelect?` | (option) => void | — | Fires only when a suggestion is chosen, with that option. |
58
+ | `onValueChange?` | (value) => void | — | Fires as the text changes (typing, picking a suggestion, or clearing) with
59
+ the new field text. |
60
+ | `placeholder?` | string | — | Placeholder text. |
61
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Field size. |
62
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation status. |
63
+ | `statusIcon?` | (string & {}) \| false \| IconShape | — | Status icon override, or `false` to drop it. |
64
+ | `tone?` | string | — | Custom accent colour — a named tone or any CSS colour. |
65
+ | `trailing?` | ReactNode | — | Trailing adornment inside the field. |
66
+ | `value?` | string | — | Controlled value: the field's text, a **free** string not constrained to a
67
+ suggestion. Leave undefined for uncontrolled. |
68
+
69
+ `InputAutocomplete` combines an input with a Menu. Reproducing its filtering,
70
+ selection, focus, and popup coordination without the React or Preact wrapper is
71
+ a substantial amount of code.
72
+
73
+ ### Native HTML input with autocomplete
74
+
75
+ For browser-native suggestions, pair a native input with a `<datalist>`. It
76
+ keeps free-form entry and lets the browser filter and fill matching options,
77
+ while `data-anta` supplies the field appearance.
78
+
79
+ The suggestions popup is owned by the browser, so use `InputAutocomplete` when
80
+ you need custom option content, popup styling, or consistent interaction across
81
+ browsers.
82
+
83
+ ```html
84
+ <input data-anta type="text" list="people" placeholder="Search people">
85
+
86
+ <datalist id="people">
87
+ <option value="Maya Chen">
88
+ <option value="Noah Williams">
89
+ <option value="Olivia Martinez">
90
+ <option value="Liam Johnson">
91
+ </datalist>
92
+ ```
93
+
94
+ **The field** is styled through Input's props (`size`, `status`, `tone`, `round`,
95
+ `leading` / `trailing`) and its `::part`s; set its width with `style` / `className`
96
+ (forwarded to the field). **The suggestion popover** takes Menu's `::part(menu)`.
97
+
98
+ ```tsx
99
+ // Give the field a width; tint it with a custom tone.
100
+ <InputAutocomplete tone="info" style={{ width: '260px' }} suggestions={frameworks} />
101
+ ```
@@ -0,0 +1,406 @@
1
+ # InputDate
2
+
3
+ **`InputDate`** is a date field that replaces the native `type="date"` (and, with
4
+ `time`, `type="datetime-local"`): a text field you can type into, backed by a
5
+ `Calendar` that opens when you click the field (or press ↓), marked by a
6
+ leading calendar icon. `Calendar`, the month grid it builds on, is documented lower
7
+ down.
8
+
9
+ The field accepts free text and resolves it on commit (blur / Enter): a lenient
10
+ parser reads the locale's order (`06/07/2026` is June 7 in en-US, July 6 in de-DE),
11
+ month names, and run-together digits, then rewrites the entry to the canonical
12
+ format. An unrecognized entry marks the field until you fix it. The value is an ISO
13
+ `YYYY-MM-DD` string, never a `Date`; it works controlled (`value` + `onValueChange`)
14
+ or uncontrolled (`defaultValue`), and submits under `name`.
15
+
16
+ ## The date field
17
+
18
+ Type a date and it resolves on blur or Enter, or click the field to open
19
+ the calendar and pick a day. Either way the field settles on the locale's canonical
20
+ format and the value is ISO `YYYY-MM-DD`. A click keeps focus in the field, so you
21
+ can keep typing or click a day; ↓ opens the calendar and moves focus into
22
+ the grid, and Esc closes it back to the field. While you type, the calendar
23
+ previews the entry — it jumps to that month and highlights the day — and commits it on
24
+ Enter (which also closes the calendar); an unrecognized entry keeps the
25
+ calendar open and marks the field.
26
+
27
+ ```tsx
28
+ <InputDate label="Due date" defaultValue="2026-06-15" onValueChange={(v) => save(v)} />
29
+ ```
30
+
31
+ The placeholder shows the locale's format, and parsing follows the same order, so
32
+ `de-DE` reads and writes `TT.MM.JJJJ` while `en-US` uses `MM/DD/YYYY`. `min` / `max`
33
+ bound both the calendar and what a typed date will accept.
34
+
35
+ ```tsx
36
+ <InputDate label="US English" locale="en-US" defaultValue="2026-06-15" />
37
+ <InputDate label="German" locale="de-DE" defaultValue="2026-06-15" />
38
+ ```
39
+
40
+ `time` turns it into a date-time field (value `YYYY-MM-DDTHH:mm`). The menu grows a
41
+ time row under the calendar: an [`InputTime`](./input-time.md) (segmented hour / minute,
42
+ plus AM/PM in 12-hour locales) and a confirm (✓) button. Picking a day keeps the menu
43
+ open so you can set the time. The clock follows the locale (`en-US` is 12-hour, most
44
+ others 24), overridable with `hour12`. The field parses a trailing time after a space
45
+ (`06/15/2026 2:30pm` or `… 14:30`), and typing a 24-hour hour into the 12-hour time
46
+ field converts it (18 → 6 PM).
47
+
48
+ ```tsx
49
+ {/* `size` scales the calendar, the time inputs, the AM/PM toggle, and the buttons together. */}
50
+ <InputDate size="small" label="Small" time defaultValue="2026-06-15T09:00" />
51
+ <InputDate size="medium" label="Medium" time defaultValue="2026-06-15T09:00" />
52
+ <InputDate size="large" label="Large" time defaultValue="2026-06-15T09:00" />
53
+ ```
54
+
55
+ | Prop | Type | Default | Description |
56
+ |------|------|---------|-------------|
57
+ | `clearable?` | boolean | — | Show a clear button once the field has a value. |
58
+ | `defaultValue?` | string | — | Initial value for the uncontrolled case. |
59
+ | `disabled?` | boolean | — | Disable the field and the calendar. |
60
+ | `hint?` | ReactNode | — | Helper text under the field. Replaced by a format hint while the entry is
61
+ unrecognized. |
62
+ | `hour12?` | boolean | — | Force the time cycle when `time` is on: `true` for 12-hour (AM/PM), `false`
63
+ for 24-hour. Omit to follow the locale (`en-US` → 12-hour, most others → 24). |
64
+ | `icon?` | false \| IconShape | calendar-days | Leading icon at the start of the field — the calendar affordance. Pass
65
+ another shape to change it, or `false` to drop it. |
66
+ | `label?` | ReactNode | — | Field label, above the control. |
67
+ | `locale?` | string | navigator.language | BCP-47 locale driving the display format, the placeholder mask, parsing order,
68
+ and the calendar. |
69
+ | `max?` | string | — | Latest selectable date (ISO). |
70
+ | `min?` | string | — | Earliest selectable date (ISO). Earlier days disable, and a typed date before
71
+ it stays uncommitted. |
72
+ | `name?` | string | — | Form field name — the ISO value submits under this key. |
73
+ | `offset?` | number | 4 | Gap in pixels between the field and the calendar menu. |
74
+ | `onValueChange?` | (value, attrs) => void | — | Fired after the value resolves (a recognized entry, a calendar pick, a time
75
+ change, or a clear), with the new ISO value (`''` when cleared) and a
76
+ `{ value, name }` snapshot. An unrecognized entry does not fire it. |
77
+ | `placeholder?` | string | the locale's format mask (e.g. `MM/DD/YYYY`, `MM/DD/YYYY HH:MM` with `time`) | Placeholder shown when empty. |
78
+ | `placement?` | 'left' \| 'right' \| 'bottom' \| 'top' \| 'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end' \| 'right-start' \| 'right-end' \| 'left-start' \| 'left-end' | bottom-start | Preferred placement of the calendar menu relative to the field. Same values as
79
+ `Menu`; it auto-flips and clamps when space runs out. |
80
+ | `round?` | boolean \| number \| string | — | Round the field corners — `true` for fully round, or a number / CSS length. |
81
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Field size. Also sizes the calendar and the time row. |
82
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | neutral | Validation/feedback tone. An unrecognized entry forces `critical` until fixed. |
83
+ | `time?` | boolean | — | Include a time. The value becomes ISO `YYYY-MM-DDTHH:mm`, the field parses a
84
+ trailing time after a space (`06/15/2026 14:30`, `… 2:30pm`), and the menu
85
+ shows a time row (hours : minutes, an AM/PM toggle in 12-hour locales, then a
86
+ Done button) under the calendar. Picking a day keeps the menu open so you can
87
+ set the time. |
88
+ | `value?` | string | — | Controlled value — ISO `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm` with `time`), `''`
89
+ for empty. Pair with `onValueChange`; the field and calendar follow it and a
90
+ pick only *requests* a change. |
91
+
92
+ ## Calendar
93
+
94
+ The sections below document `Calendar`, the month grid `InputDate` opens. Reach for
95
+ it directly when you want the grid inline, without a field.
96
+
97
+ `Calendar` is **composed from Anta components in light DOM**, with nothing hidden in
98
+ a shadow root. The days and prev/next chevrons are `<Button>`s (a selected day is a
99
+ `tertiary` Button in its selected state, toned `brand`), inside a form-associated `<a-calendar>`
100
+ grid. Date math runs on the [Temporal API](https://tc39.es/proposal-temporal/docs/),
101
+ bundled via `temporal-polyfill`. For a raw grid, import `buildMonth` from
102
+ `@antadesign/anta`: it returns localized weekday headers and every day record you
103
+ need to render — ISO value, accessible label, and selected, disabled, today, and
104
+ outside-month state. It does not coordinate navigation, roving focus, selection, or
105
+ re-renders for you. The value is an ISO `YYYY-MM-DD` string, never a `Date`. It works
106
+ controlled (`value` + `onStateChange`) or uncontrolled (`defaultValue`), and
107
+ participates in forms under `name`.
108
+
109
+ **Not SSR-safe.** `Calendar` (and `InputDate`, which embeds it) reads the current
110
+ date (`Temporal.Now`) and locale (`navigator.language`) at render, so a server
111
+ render disagrees with the client on the highlighted "today" and on localized
112
+ labels, and the page hydrates with a mismatch. Render it client-side only: Astro
113
+ `client:only`, or a dynamic import inside `useEffect`. Every demo on this page uses
114
+ `client:only` for that reason.
115
+
116
+ ## Selecting a date
117
+
118
+ Click a day, or focus the grid and press Enter / Space. The
119
+ selected day takes the tertiary-button selected look toned brand — a brand-tinted
120
+ filled cell with a ring. Today shows as a secondary Button, a resting fill.
121
+
122
+ ```tsx
123
+ <Calendar defaultValue="2026-06-12" />
124
+ ```
125
+
126
+ ## Size
127
+
128
+ `size` scales the whole calendar: the day cells, the prev/next chevrons, and the
129
+ weekday and month-heading type. It follows Button's `small` / `medium` (default) /
130
+ `large` scale, with day-cell heights of 24 / 28 / 32px.
131
+
132
+ ```tsx
133
+ <Calendar size="small" defaultValue="2026-06-28" />
134
+ <Calendar defaultValue="2026-06-28" /> {/* medium (default) */}
135
+ <Calendar size="large" defaultValue="2026-06-28" />
136
+ ```
137
+
138
+ ## Range
139
+
140
+ `min` and `max` (ISO strings) bound the selectable range. Days outside it render
141
+ disabled and skip keyboard focus, and the prev/next arrows stop at the range edge.
142
+ The jump menu adapts to how far the range reaches.
143
+
144
+ No `min` / `max`: the jump menu lists today ±3 years, each a submenu of its months.
145
+
146
+ ```tsx
147
+ <Calendar size="small" defaultValue="2026-06-15" />
148
+ ```
149
+
150
+ A range across years lists each year (here 2026 and 2027) as a submenu of months.
151
+ June 2026 disables days 1–7, and the back arrow stops at the range's start.
152
+
153
+ ```tsx
154
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2027-06-24" />
155
+ ```
156
+
157
+ A range inside one year skips the year level and lists months directly, March
158
+ through November enabled and the rest disabled.
159
+
160
+ ```tsx
161
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-03-01" max="2026-11-30" />
162
+ ```
163
+
164
+ A range inside one month leaves nowhere to jump. Both arrows and the month/year
165
+ button are disabled, and only the in-range days stay selectable.
166
+
167
+ ```tsx
168
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2026-06-24" />
169
+ ```
170
+
171
+ ## Locale
172
+
173
+ `Calendar` respects the browser locale by default (`navigator.language`). Pass a
174
+ BCP-47 `locale` to override it: the first day of week comes from the locale's week
175
+ info (never hard-coded to Sunday), and the weekday and month names are localized.
176
+
177
+ ```tsx
178
+ <Calendar defaultValue="2026-06-28" locale="en-US" /> {/* week starts Sunday */}
179
+ <Calendar defaultValue="2026-06-28" locale="fr-FR" /> {/* week starts Monday */}
180
+ <Calendar defaultValue="2026-06-28" locale="ja-JP" />
181
+ ```
182
+
183
+ ## Controlled
184
+
185
+ Uncontrolled by default: pass `defaultValue` and read the date from `onValueChange`
186
+ or at form submit. Make it controlled with `value` + `onStateChange` to drive it
187
+ from your store. The grid then follows `value`, and a pick only *requests* a change:
188
+ answer by updating `value`, reject by doing nothing.
189
+
190
+ ```tsx
191
+ const [date, setDate] = useState('2026-06-28')
192
+
193
+ <Calendar
194
+ value={date}
195
+ onStateChange={(_e, { next }) => setDate(next ?? '')}
196
+ />
197
+ ```
198
+
199
+ `onStateChange` fires before the element applies, with `(event, { next, prev, reason })`
200
+ where `next` / `prev` are ISO strings. For a `'user'` pick, `event.preventDefault()`
201
+ vetoes it (uncontrolled). A controlled calendar with no `onStateChange` is read-only.
202
+
203
+ ## Disabled
204
+
205
+ `disabled` dims the whole calendar and removes it from interaction.
206
+
207
+ ```tsx
208
+ <Calendar defaultValue="2026-06-28" disabled />
209
+ ```
210
+
211
+ ## Forms
212
+
213
+ `Calendar` is form-associated. Give it a `name` and it submits the selected ISO
214
+ date under that key, resets to `defaultValue` on form reset, and reports through
215
+ the standard constraint-validation pipeline.
216
+
217
+ ```tsx
218
+ <form>
219
+ <Calendar name="due-date" defaultValue="2026-06-28" />
220
+ </form>
221
+ ```
222
+
223
+ ## Accessibility
224
+
225
+ The calendar is a labelled **group** of day buttons, a flat grid rather than a
226
+ nested `grid`/`row`/`gridcell` tree. The group is named by the visible month
227
+ (`aria-labelledby`). Each day is a `<button>` whose `aria-label` is its full date
228
+ including weekday ("Sunday, June 28, 2026"), with `aria-pressed` on the selected day
229
+ and `aria-current="date"` on today. The decorative weekday headers are `aria-hidden`,
230
+ since each day already names its weekday. A roving `tabindex` keeps a single tab
231
+ stop, and an `aria-live` region announces the month when it changes.
232
+
233
+ | Key | Moves focus |
234
+ |---|---|
235
+ | ← / → | by one day |
236
+ | ↑ / ↓ | by one week |
237
+ | Home / End | to the start / end of the week |
238
+ | PageUp / PageDown | by one month |
239
+ | Shift + PageUp / PageDown | by one year |
240
+ | Enter / Space | select the focused day |
241
+
242
+ ## Customization
243
+
244
+ The family stacks in layers: `InputDate` composes `Input` + `Menu` + `Calendar`,
245
+ and `Calendar` itself splits into the `<a-calendar>` element (the day grid) and a
246
+ JSX wrapper (the navigation chrome). Knowing which layer owns what tells you where to
247
+ reach when you extend it, and lets a non-React consumer rebuild the chrome from stock
248
+ elements.
249
+
250
+ ### Components composition
251
+
252
+ `InputDate` has no `a-inputdate` element; the wrapper is the coordinator, the role
253
+ `Select` plays over `Input` + `Menu`. It renders an editable `<Input>` trigger and a
254
+ `<Menu>` holding a `<Calendar>` (with `time`, the hour/minute row and AM/PM `Tabs`
255
+ sit under the grid), and owns the draft text, the lenient parse on commit, and the
256
+ ISO value. The menu anchors to the field and opens on click (via the `Menu`'s own
257
+ anchor-click trigger, like `Select`) — a mouse open leaves focus in the field so you
258
+ can keep typing, while ↓ opens it and moves focus into the grid. That
259
+ focus move is declarative: `InputDate` bumps `Calendar`'s `focusSignal`, which
260
+ renders a `data-focus` attribute the `<a-calendar>` element reads to focus the active
261
+ day — the wrapper never touches the DOM. Nesting a stateful component in a menu has
262
+ one wrinkle: the `Calendar` (and `Tabs`) dispatch their own
263
+ `statechange`, so the `Menu` acts only on its own `open` / `closed`, and a bubbled
264
+ pick isn't mistaken for a dismiss.
265
+
266
+ The `<a-calendar>` element owns the grid and nothing else. It wires the day
267
+ `<button>`s you render into it (click and keyboard: arrows, Home/End,
268
+ PageUp/PageDown), holds the submitted value via `ElementInternals`,
269
+ and dispatches the events: `statechange` / `change` on a pick, and `navigate` when a
270
+ key pages past the rendered month. It's framework-agnostic and works on its own, with
271
+ no notion of a "current month" and no sight of `min` / `max`.
272
+
273
+ Everything above the grid is the JSX wrapper, rendered as **siblings outside**
274
+ `<a-calendar>` from ordinary components: the prev/next arrows are `<Button>`s, and the
275
+ month/year picker is a `<Button>` opening a `<Menu>` of year `MenuItem`s, each a
276
+ submenu of months. The wrapper holds the displayed-month cursor in state, derives the
277
+ day cells and the arrows' `disabled` state from `min` / `max`, and turns a click on an
278
+ arrow, a pick in the menu, or a `navigate` event into a new cursor, then re-renders
279
+ the grid. So range enforcement (disabled days, capped arrows, a disabled picker for a
280
+ single-month range) lives entirely in the wrapper; the element only refuses to
281
+ activate a day you've rendered `disabled`.
282
+
283
+ That seam is why the element has no `min` / `max` and the switcher is a sibling: the
284
+ grid stays a small, portable authority, and the chrome (Anta `Button`s and a `Menu`)
285
+ is composition you can replace or rebuild. **Without React**, render the grid from the
286
+ exported engine (`buildMonth`) and drive it with the same glue the wrapper packages:
287
+
288
+ ```html
289
+ <!-- `.calendar` is your own wrapper; the switcher is plain Buttons + a Menu. -->
290
+ <div class="calendar" style="display: inline-flex; flex-direction: column; gap: 6px">
291
+ <div style="display: flex; align-items: center">
292
+ <a-button priority="tertiary" data-prev aria-label="Previous month"><a-icon shape="chevron-left"></a-icon></a-button>
293
+ <a-button priority="tertiary" data-heading style="flex: 1; justify-content: center" aria-haspopup="menu"></a-button>
294
+ <a-button priority="tertiary" data-next aria-label="Next month"><a-icon shape="chevron-right"></a-icon></a-button>
295
+ </div>
296
+ <!-- The element wires whatever day cells you render; it owns selection,
297
+ keyboard, the form value, and the events. -->
298
+ <a-calendar role="group" name="date"></a-calendar>
299
+ </div>
300
+ <script type="module">
301
+ import '@antadesign/anta/elements'
302
+ import { buildMonth, parseISODate } from '@antadesign/anta'
303
+ import { Temporal } from 'temporal-polyfill'
304
+
305
+ const root = document.querySelector('.calendar')
306
+ const grid = root.querySelector('a-calendar')
307
+ const heading = root.querySelector('[data-heading]')
308
+ let cursor = Temporal.Now.plainDateISO()
309
+
310
+ function render() {
311
+ const m = buildMonth({ anchor: cursor, locale: navigator.language })
312
+ heading.textContent = m.heading
313
+ grid.replaceChildren()
314
+ for (const w of m.weekdays) {
315
+ const s = Object.assign(document.createElement('span'), { textContent: w.narrow })
316
+ s.setAttribute('data-part', 'weekday')
317
+ s.setAttribute('aria-hidden', 'true')
318
+ grid.append(s)
319
+ }
320
+ for (const d of m.weeks.flat()) {
321
+ const b = Object.assign(document.createElement('a-button'), { textContent: String(d.day) })
322
+ b.setAttribute('priority', 'tertiary')
323
+ b.setAttribute('data-part', 'day-cell')
324
+ b.setAttribute('data-date', d.iso) // the element keys off this
325
+ b.tabIndex = d.iso === cursor.toString() ? 0 : -1
326
+ if (d.selected) b.setAttribute('selected', '')
327
+ if (d.disabled) b.setAttribute('disabled', '')
328
+ grid.append(b)
329
+ }
330
+ }
331
+
332
+ root.querySelector('[data-prev]').addEventListener('click', () => { cursor = cursor.subtract({ months: 1 }); render() })
333
+ root.querySelector('[data-next]').addEventListener('click', () => { cursor = cursor.add({ months: 1 }); render() })
334
+ // Keyboard paging past the rendered grid: the element asks for a month flip.
335
+ grid.addEventListener('navigate', (e) => { cursor = parseISODate(e.detail.date) ?? cursor; render() })
336
+ render()
337
+ </script>
338
+ ```
339
+
340
+ Tracking the cursor, painting the grid from `buildMonth`, and re-rendering on
341
+ `navigate` is what the React `Calendar` packages, plus the year/month jump menu,
342
+ which uses the same `Menu` composition shown on the [Menu](./menu.md) page.
343
+
344
+ `InputDate` has no host element. Rebuilding its text parsing, dropdown calendar,
345
+ localized day grid, month navigation, keyboard navigation, and focus management
346
+ in vanilla JavaScript is significant work, even with Anta's `buildMonth` and
347
+ `parseISODate` helpers. Use the `InputDate` wrapper when React or Preact is an
348
+ option. Otherwise, a native HTML date input is the practical baseline.
349
+
350
+ ### Native HTML date input
351
+
352
+ Add `data-anta` to a `date`, `datetime-local`, `month`, `time`, or `week` input.
353
+ Nothing else is required: Anta styles the field while the browser keeps its picker,
354
+ icon, validation, and keyboard model. The picker itself and its date/time
355
+ formatting remain browser- and OS-owned.
356
+
357
+ `data-anta-size`, `round`, and a custom-color `tone` use the matching Input
358
+ field treatments.
359
+
360
+ ```html
361
+ <!-- `data-anta` styles the field, not the native picker. -->
362
+ <input data-anta type="date" name="due-date" value="2026-06-15">
363
+ <input data-anta type="datetime-local" name="starts-at" value="2026-06-15T09:00">
364
+ <input data-anta type="month" name="billing-month" value="2026-06">
365
+ <input data-anta type="time" name="reminder-at" value="09:00">
366
+ <input data-anta type="week" name="sprint-week" value="2026-W25">
367
+ ```
368
+
369
+ The calendar is **light DOM**, so everything is plain CSS, with no shadow parts or
370
+ hidden internals. The days and chevrons are real `<a-button>`s, so their colours,
371
+ hover, focus ring, and selected look come from the Button styles and theme
372
+ themselves; they sit inside the `<a-calendar>` grid, and the switcher header sits in
373
+ the `.anta-calendar` shell. An un-layered consumer rule beats `@layer anta` without
374
+ `!important`. The class below is for the demo.
375
+
376
+ ```css
377
+ /* Bigger, circular day cells. `className` lands on the outer .anta-calendar
378
+ shell; `--calendar-cell-width` / `--calendar-cell-height` drive the grid track
379
+ and the cell box (inherited down), and you target the day Buttons directly. */
380
+ .anta-calendar.roomy { --calendar-cell-width: 40px; --calendar-cell-height: 40px; }
381
+ .anta-calendar.roomy a-button[data-part="day-cell"] { border-radius: 50%; }
382
+ ```
383
+
384
+ Each day is a `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`, so a
385
+ specific date is addressable by its `data-date`: tint it, or hang an event marker
386
+ off a `::after` (give the cell `position: relative` first). Below, three days carry a
387
+ brand dot and one is flagged in the critical colour.
388
+
389
+ ```css
390
+ /* Day cells are `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`. Give them a
391
+ positioning context, dot event days with a `::after`, and tint a date's text. */
392
+ .anta-calendar.events a-button[data-part="day-cell"] { position: relative; }
393
+ .anta-calendar.events a-button[data-part="day-cell"]:is(
394
+ [data-date="2026-06-05"], [data-date="2026-06-12"], [data-date="2026-06-20"]
395
+ )::after {
396
+ content: ""; position: absolute; inset-block-end: 3px; inset-inline-start: 50%;
397
+ inline-size: 4px; block-size: 4px; transform: translateX(-50%);
398
+ border-radius: 999px; background: var(--text-2-brand);
399
+ }
400
+ .anta-calendar.events a-button[data-part="day-cell"][data-date="2026-06-25"] {
401
+ color: var(--text-2-critical); font-weight: 600;
402
+ }
403
+ ```
404
+
405
+ The engine (`buildMonth`, `firstDayOfWeek`, …) is exported, so you can render a
406
+ fully custom grid and keep only the `<a-calendar>` form plumbing.