+
+ Forms · MonthGrid
+ MonthGrid
+
+ Standalone month picker — 12 cells (Jan..Dec) for a single year, prev/next year navigation,
+ full keyboard support and morfo-declared semantic events. Parallel to the inline
+ Calendar's month view but works without a calendar context.
+
+
+
+ parts {compiled.parts.order.length}
+
+
+ events {events.length}
+
+
+ value {valueLabel}
+
+
+ year {placeholderLabel}
+
+
+
+
+
+
+
+ {#snippet children({ months }: MonthGridProviderSnippetProps)}
+
+ <
+
+ >
+
+
+ {#each months as month (month)}
+
+ {/each}
+
+ {/snippet}
+
+
+
+ trace
+ {#if trace.length === 0}
+ click a month or use prev/next
+ {:else}
+ {#each trace.slice(0, 3) as entry}
+ {entry.event} · {entry.family}{entry.intent
+ ? ' · ' + entry.intent
+ : ''}
+ {fmtTime(entry.at)}
+ {/each}
+ {/if}
+
+ value
+ {valueLabel}
+
+
+
+
+
+ (tab = 'live')}>Live
+ (tab = 'api')}>API
+ (tab = 'morfo')}>
+ morfo
+ {partsList.length}p · {events.length}e
+
+ (tab = 'sema')}>
+ sema
+ {events.length}
+
+ (tab = 'recipe')}>Recipe
+ (tab = 'a11y')}>A11y
+
+
+ {#if tab === 'live'}
+
+ Controls
+
+ soma props · value, placeholder, bounds
+
+
+
+ value {valueLabel}
+
+ (value = { year: 2026, month: 5 })}>May 2026
+ (value = { year: 2026, month: 6 })}>Jun 2026
+ (value = undefined)}>empty
+
+
+
+ placeholder year
+
+ {#each [2025, 2026, 2027] as y (y)}
+ (placeholder = { year: y })}>{y}
+ {/each}
+
+
+
+ state flags
+ disabled
+ readonly
+
+
+ bounds
+ min=Mar 2026
+ max=Oct 2026
+
+
+ monthFormat
+
+ {#each ['long', 'short', 'narrow'] as v (v)}
+ (monthFormat = v as typeof monthFormat)}>{v}
+ {/each}
+
+
+
+ locale
+
+ en-US
+ es-ES
+ fr-FR
+
+
+
+ direction
+
+ {#each ['ltr', 'rtl'] as v (v)}
+ (dir = v as 'ltr' | 'rtl')}>{v}
+ {/each}
+
+
+
+
+
+ eidos props · visual treatment
+
+
+
+ size
+
+ {#each ['sm', 'md', 'lg'] as v (v)}
+ (size = v as MonthGridSize)}>{v}
+ {/each}
+
+
+
+ variant
+
+ {#each ['surface', 'outline', 'ghost'] as v (v)}
+ (variant = v as MonthGridVariant)}>{v}
+ {/each}
+
+
+
+ color
+
+ {#each ['primary', 'secondary', 'neutral', 'affirm', 'risk'] as v (v)}
+ (color = v as MonthGridColor)}>{v}
+ {/each}
+
+
+
+
+
+
+ soma
+ headless · standalone month picker
+ svelte
+
+
{somaSnippet}
+
+
+
+ eidos
+ visual · root plus parts
+ svelte
+
+
{eidosSnippet}
+
+
+ {/if}
+
+ {#if tab === 'api'}
+
+ API reference
+
+
+ Prop Owner Default Description
+
+ value bindable soma undefined Selected `{`{ year, month }`}` pair.
+ placeholder bindable soma today's year Anchor year for the 12-month grid.
+ minValue / maxValue soma - Disables cells outside the bound.
+ disabled / readonly soma false Root state.
+ monthFormat soma 'short' Intl month-name length.
+ locale / dir soma services Label formatting and directional arrow keys.
+ size eidos eidos md Visual scale.
+ variant eidos eidos surface Surface treatment.
+ color eidos eidos primary Intent palette for selected month.
+
+
+
+
+ Reference comparison
+
+
+ Feature Ark UI React Aria Chakra Bits UI UIX
+
+ Standalone month grid part of DatePicker via composition no via Calendar first-class
+ Bounds (min/max) yes yes - yes yes
+ Keyboard nav yes yes - yes yes
+ Locale labels yes yes - yes yes
+ Semantic events callbacks callbacks - callbacks commit-set / nav-step
+
+
+
+
+ {/if}
+
+ {#if tab === 'morfo'}
+
+
+ morfo · declarative contract
+
+
+
+ Part Marker Element Role Archetype Optional
+
+ {#each partsList as part (part.kebab)}
+
+ {part.kebab}
+ [{part.marker}]
+ <{part.defaultElement}>
+ {part.role ?? '-'}
+ {part.archetype ?? '-'}
+ {part.optional ? 'yes' : 'no'}
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'sema'}
+
+
+ sema · events
+
+
+
+ Name Family Verb Sequence Intent Target Play
+
+ {#each events as action (action.name)}
+ {@const intentDecl = action.semantic.intent}
+
+ {action.name}
+ {action.semantic.family}
+ {action.semantic.verb ?? '-'}
+ {action.semantic.sequence ?? 'pre'}
+ {typeof intentDecl === 'string' ? intentDecl : intentDecl ? 'fromProp' : '-'}
+ {action.target}
+ playEvent(action)}>▶ play
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'recipe'}
+
+ Eidos recipe
+
+
+ Selector Purpose
+
+ [data-month-grid]Root surface, size and variant tokens.
+ [data-month-grid-header]Three-column nav header (prev · heading · next).
+ [data-month-grid-grid]3×4 month grid.
+ [data-month-grid-cell][data-selected]Selected month tile.
+ [data-month-grid-cell][data-current]Today's month accent.
+ [data-month-grid-cell][data-disabled]Out of bounds / disabled.
+
+
+
+
+ {/if}
+
+ {#if tab === 'a11y'}
+
+ Accessibility
+ Keyboard
+
+
+ Key Action
+
+ ← / → Move focus ±1 month (RTL-flipped).
+ ↑ / ↓ Move focus ±3 months (row width).
+ Home / End First / last month of year.
+ PageUp / PageDown ±1 year (also moves placeholder).
+ Enter / Space Select focused month.
+
+
+
+ APG
+ Follows WAI-ARIA APG Grid pattern: role="grid" on the grid container, role="gridcell" + roving tabindex on cells.
+
+ {/if}
+
diff --git a/web/routes/uix/components/year-grid/+page.svelte b/web/routes/uix/components/year-grid/+page.svelte
new file mode 100644
index 000000000..88923e210
--- /dev/null
+++ b/web/routes/uix/components/year-grid/+page.svelte
@@ -0,0 +1,492 @@
+
+
+
+
+ Forms · YearGrid
+ YearGrid
+
+ Standalone year picker — a paged grid of years (default decade size 12) with prev/next
+ page navigation, full keyboard support and morfo-declared semantic events. Parallel to the
+ DatePicker's year view but works without a date-picker context.
+
+
+
+ parts {compiled.parts.order.length}
+
+
+ events {events.length}
+
+
+ value {valueLabel}
+
+
+ anchor {placeholderLabel}
+
+
+
+
+
+
+
+ {#snippet children({ years }: YearGridProviderSnippetProps)}
+
+ <
+
+ >
+
+
+ {#each years as year (year)}
+
+ {/each}
+
+ {/snippet}
+
+
+
+ trace
+ {#if trace.length === 0}
+ click a year or use prev/next
+ {:else}
+ {#each trace.slice(0, 3) as entry}
+ {entry.event} · {entry.family}{entry.intent
+ ? ' · ' + entry.intent
+ : ''}
+ {fmtTime(entry.at)}
+ {/each}
+ {/if}
+
+ value
+ {valueLabel}
+
+
+
+
+
+ (tab = 'live')}>Live
+ (tab = 'api')}>API
+ (tab = 'morfo')}>
+ morfo
+ {partsList.length}p · {events.length}e
+
+ (tab = 'sema')}>
+ sema
+ {events.length}
+
+ (tab = 'recipe')}>Recipe
+ (tab = 'a11y')}>A11y
+
+
+ {#if tab === 'live'}
+
+ Controls
+
+ soma props · value, placeholder, bounds
+
+
+
+ value {valueLabel}
+
+ {#each [2024, 2025, 2026, 2027] as y (y)}
+ (value = { year: y })}>{y}
+ {/each}
+ (value = undefined)}>empty
+
+
+
+ placeholder year
+
+ {#each [2020, 2026, 2032] as y (y)}
+ (placeholder = { year: y })}>{y}
+ {/each}
+
+
+
+ state flags
+ disabled
+ readonly
+
+
+ bounds
+ min=2024
+ max=2030
+
+
+ decadeSize
+
+
+
+ locale
+
+ en-US
+ es-ES
+ ar-EG
+
+
+
+ direction
+
+ {#each ['ltr', 'rtl'] as v (v)}
+ (dir = v as 'ltr' | 'rtl')}>{v}
+ {/each}
+
+
+
+
+
+ eidos props · visual treatment
+
+
+
+ size
+
+ {#each ['sm', 'md', 'lg'] as v (v)}
+ (size = v as YearGridSize)}>{v}
+ {/each}
+
+
+
+ variant
+
+ {#each ['surface', 'outline', 'ghost'] as v (v)}
+ (variant = v as YearGridVariant)}>{v}
+ {/each}
+
+
+
+ color
+
+ {#each ['primary', 'secondary', 'neutral', 'affirm', 'risk'] as v (v)}
+ (color = v as YearGridColor)}>{v}
+ {/each}
+
+
+
+
+
+
+ soma
+ headless · standalone year picker
+ svelte
+
+
{somaSnippet}
+
+
+
+ eidos
+ visual · root plus parts
+ svelte
+
+
{eidosSnippet}
+
+
+ {/if}
+
+ {#if tab === 'api'}
+
+ API reference
+
+
+ Prop Owner Default Description
+
+ value bindable soma undefined Selected `{`{ year }`}`.
+ placeholder bindable soma today's year Anchor year for the visible page.
+ minValue / maxValue soma - Disables cells outside the bound.
+ decadeSize soma 12 Years per page.
+ disabled / readonly soma false Root state.
+ locale / dir soma services Year formatting and directional arrow keys.
+ size eidos eidos md Visual scale.
+ variant eidos eidos surface Surface treatment.
+ color eidos eidos primary Intent palette for selected year.
+
+
+
+
+ Reference comparison
+
+
+ Feature Ark UI React Aria Chakra Bits UI UIX
+
+ Standalone year grid part of DatePicker via composition no via Calendar first-class
+ Configurable page size fixed 12 fixed 12 - fixed 12 decadeSize
+ Bounds (min/max) yes yes - yes yes
+ Keyboard nav yes yes - yes yes
+ Semantic events callbacks callbacks - callbacks commit-set / nav-step
+
+
+
+
+ {/if}
+
+ {#if tab === 'morfo'}
+
+
+ morfo · declarative contract
+
+
+
+ Part Marker Element Role Archetype Optional
+
+ {#each partsList as part (part.kebab)}
+
+ {part.kebab}
+ [{part.marker}]
+ <{part.defaultElement}>
+ {part.role ?? '-'}
+ {part.archetype ?? '-'}
+ {part.optional ? 'yes' : 'no'}
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'sema'}
+
+
+ sema · events
+
+
+
+ Name Family Verb Sequence Intent Target Play
+
+ {#each events as action (action.name)}
+ {@const intentDecl = action.semantic.intent}
+
+ {action.name}
+ {action.semantic.family}
+ {action.semantic.verb ?? '-'}
+ {action.semantic.sequence ?? 'pre'}
+ {typeof intentDecl === 'string' ? intentDecl : intentDecl ? 'fromProp' : '-'}
+ {action.target}
+ playEvent(action)}>▶ play
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'recipe'}
+
+ Eidos recipe
+
+
+ Selector Purpose
+
+ [data-year-grid]Root surface, size and variant tokens.
+ [data-year-grid-header]Three-column nav header (prev · heading · next).
+ [data-year-grid-grid]3-column year grid.
+ [data-year-grid-cell][data-selected]Selected year tile.
+ [data-year-grid-cell][data-current]Current year accent.
+ [data-year-grid-cell][data-disabled]Out of bounds / disabled.
+
+
+
+
+ {/if}
+
+ {#if tab === 'a11y'}
+
+ Accessibility
+ Keyboard
+
+
+ Key Action
+
+ ← / → Move focus ±1 year (RTL-flipped).
+ ↑ / ↓ Move focus ±3 years (3-col layout).
+ Home / End First / last year of page.
+ PageUp / PageDown ±decadeSize years (page step).
+ Enter / Space Select focused year.
+
+
+
+ APG
+ Follows WAI-ARIA APG Grid pattern: role="grid" on the grid container, role="gridcell" + roving tabindex on cells.
+
+ {/if}
+