BetaNavigation

AgendaCalendar

One week, and what is on it.

Overview

One week, and what is on it.

First example

import { AgendaCalendar } from '@xaui/native/agenda-calendar'
export function Example() {
return <AgendaCalendar />
}

Anatomy

<AgendaCalendar value={day} onValueChange={setDay} events={dates}>
  <AgendaCalendar.Header>
    <AgendaCalendar.Title />
    <AgendaCalendar.Nav>
      <AgendaCalendar.PreviousButton accessibilityLabel="Semaine précédente" />
      <AgendaCalendar.TodayButton>Today</AgendaCalendar.TodayButton>
      <AgendaCalendar.NextButton accessibilityLabel="Semaine suivante" />
    </AgendaCalendar.Nav>
  </AgendaCalendar.Header>
  <AgendaCalendar.Weekdays />
  <AgendaCalendar.Week />
</AgendaCalendar>
slotwhat it is
AgendaCalendarThe card, the state, the resolved style
AgendaCalendar.HeaderThe row above the strip
AgendaCalendar.TitleThe month the week is in
AgendaCalendar.NavThe cluster on the trailing end
AgendaCalendar.PreviousButtonA week back. Dead at the bounds
AgendaCalendar.NextButtonA week forward
AgendaCalendar.TodayButtonBack to this week. It does not choose today
AgendaCalendar.WeekdaysThe seven column headings
AgendaCalendar.WeekThe seven days
AgendaCalendar.DayOne day, and the mark under it
AgendaCalendar.MonthPickerThe twelve months, in a row where the days were
AgendaCalendar.YearPickerThe years, in a row where the days were

Unlike the Calendar, this one draws its own card: a surface ground, a corner and its padding. A strip is a thing you put on a screen; a month grid is a thing you put in a sheet.

Usage

import { AgendaCalendar } from '@xaui/native/agenda-calendar'

Root props

AgendaCalendarProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
variantCalendarVariant | undefinedNo
sizeAgendaCalendarSize | undefinedNoThe cell's box and the type around it. The strip spans its parent either way.
radiusRadiusKey | undefinedNoThe chosen day's corner, which is a circle by default.
colorstring | undefinedNoA raw tint (`'#7c3aed'`), never a token (R7). It lands on the chosen day.
valueDate | undefinedNoThe chosen day. Controlled — leave it out and the strip holds its own.
defaultValueDate | undefinedNo
onValueChange((value: Date) => void) | undefinedNo
weekDate | undefinedNoAny day in the week on screen. Controlled separately from the value, for the `Calendar`'s reason: scrolling through weeks is not choosing a day.
defaultWeekDate | undefinedNo
onWeekChange((week: Date) => void) | undefinedNo
viewAgendaCalendarView | undefinedNoWhich strip is on screen — the days, the months, or the years. Controlled separately again: the picker components step it, and a title button of the caller's opens it.
defaultViewAgendaCalendarView | undefinedNoThe strip shown at first mount.
onViewChange((view: AgendaCalendarView) => void) | undefinedNo
eventsreadonly Date[] | undefinedNoThe days that have something on them. Each one gets a mark under its number, which is the whole of what makes this an *agenda* rather than a week of numbers. A list rather than a predicate because that is what a caller has: a month of events mapped to their dates. It is read by day, so the time each one carries is ignored.
minValueDate | undefinedNo
maxValueDate | undefinedNo
firstDayOfWeekWeekDay | undefinedNo
localestring | undefinedNoNames the month and the weekdays.
isDisabledboolean | undefinedNo
styleStyleProp<ViewStyle>No
childrenReactNodeNo

Slots

AgendaCalendarDayProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
dateDateYes
childrenReactNodeNo
isDisabledboolean | undefinedNo
styleStyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)No

AgendaCalendarMonthPickerProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
format"long" | "short" | undefinedNoHow the month names read. `'long'` is "septembre", `'short'` is "sept." — a row that scrolls sideways, so a long name is a wider pill rather than a wrapped one.
childrenReactNodeNo

AgendaCalendarNavButtonProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo
stepnumber | undefinedNoHow many units one press moves — weeks, months or years, by the strip on screen.

AgendaCalendarTextSlotProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo

AgendaCalendarViewSlotProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo

AgendaCalendarWeekProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNode | ((date: Date) => ReactNode)NoThe `Calendar.Grid`'s escape hatch, for the same reason: seven cells from a date.
swipeableboolean | undefinedNoA horizontal drag on the strip pages the week — left for the next, right for the previous — so the chevrons are a shortcut rather than the only way. Set `false` to turn it off. It runs on `react-native-gesture-handler`, an **optional** peer of this package: turning this on is what pulls it in, so a `<GestureHandlerRootView>` has to be above the strip.

AgendaCalendarYearPickerProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
firstYearnumber | undefinedNoFirst year in the row. Defaults to fifty back — from `minValue`'s year when bounded.
lastYearnumber | undefinedNoLast year in the row. Defaults to fifty on — from `maxValue`'s year when bounded.
childrenReactNodeNo

Variants, sizes and colour

Sizes

sizeCellNav buttonPill
sm363212/16
md403614/20
lg444016/24

The cell is the Calendar's at the same size, so a strip and a month at md line up.

Variants and colour

The Calendar's four, on the same tokens, because the chosen day is the same day. The Today pill answers the same variant: the variant is the card's one level of emphasis, and a strip whose chosen day is a soft wash under a pill that kept a hard accent border is two variants on one component.

variantChosen dayToday pill
primaryAccent discBordered, the word in the accent
secondarySoft accent discSoft accent fill
tertiaryNeutral discNeutral fill
ghostInverted discThe word alone

The emphasis runs the other way round from a Button's, and on purpose: primary outlines rather than fills, because the pill sits between two bare chevrons and a filled accent there would read as the primary action of the whole card. The accent goes on the word instead.

color is a raw value (R7) and reaches the disc, the mark on it, and whatever the variant gave the pill — the border and the word on primary, the fill on secondary and tertiary, the word on ghost.

Accessibility

  • Each day is a button labelled with its full date, and a marked day says so in its label: a dot is invisible to a screen reader otherwise. The word is one language, which is the honest limit — a caller with two passes accessibilityLabel, and that wins.
  • The weekday row is hidden from the accessibility tree; each day announces its own date.
  • The chevrons need an accessibilityLabel from the caller, for the Calendar's reason.

Migration from legacy

This component declares no rule of its own. The migration guide covers variants, colours and slots.

Implementation notes

It is a `Calendar` folded down to a week

And the row of marks is the whole difference. A strip of seven numbers is a date picker. A strip of seven numbers with marks under some of them is an agenda.

<AgendaCalendar events={[new Date(2026, 8, 7), new Date(2026, 8, 9)]} />

events is a list rather than a predicate, because a list is what a caller has: a month of events mapped to their dates. It is read by day, so the time each one carries is ignored, and it is turned into a set once per change rather than scanned per cell.

The cells are the Calendar's own style, resolved through calendarRecipe rather than a second table. A strip and a month showing two different discs for the same chosen day is what that sharing exists to prevent — and the two sit one above the other the moment a caller expands one into the other, which is what the chevron beside the title is usually for.

Why it is a component rather than a prop

It steps by weeks. That is a different unit from the Calendar's month, so the state under it is different too: a layout="week" prop would have had to mean "and now month is a week", which is a prop that changes what another prop means.

Everything genuinely shared — the cell, the disc, the mark, the column headings, the date arithmetic — is shared. The API is not, because the two do not do the same thing.

No day is ever "outside"

All seven are on screen and all seven are choosable. A strip that greyed out the two days belonging to next month would be greying out days it is showing.

Only minValue / maxValue make a day inert. The chevrons go dead when the week they would reach has no selectable day in it — the Calendar's rule, one unit down.

Swipe to page the week

A horizontal drag on AgendaCalendar.Week turns the week — left for the next, right for the previous — so the chevrons are a shortcut rather than the only way. The strip follows the finger a little and springs back; past ~48pt, or a fast flick, the week changes. A drag that would land on a week with no selectable day does nothing, exactly like the dead chevron.

A tap on a day never starts a drag, and a vertical drag is handed straight back to whatever scrolls the page. swipeable={false} turns the gesture off — and with it the react-native-gesture-handler requirement.

The week and the day are two pieces of state

<AgendaCalendar
  value={day}
  onValueChange={setDay}
  week={week}
  onWeekChange={setWeek}
/>

Paging weeks is not choosing a day, for the reason calendar.md gives about months.

Today, in one press

AgendaCalendar.TodayButton brings today's week on screen, closes any month or year row that is open, and chooses today — the same three things legacy's dialog did. A button that dropped you on the right week and left you to find the day would be half a button. select still honours the bounds, so an out-of-range today moves the strip without choosing.

It goes dead, and reads dead, only when there is nothing left for it to do: today's week is showing, the day strip is the view, and today is the chosen day. Page away, open a picker, or choose another day and it comes back to life.

The word is the caller's — "Today", "Aujourd'hui", "Hoy" — and R3 wraps a text child into the label.

Changing the month and the year

const [view, setView] = useState<AgendaCalendarView>('week')

<AgendaCalendar value={day} onValueChange={setDay} view={view} onViewChange={setView}>
  <AgendaCalendar.Header>
    <PressableFeedback onPress={() => setView(v => (v === 'week' ? 'year' : 'week'))}>
      <AgendaCalendar.Title />
    </PressableFeedback>
    <AgendaCalendar.Nav>
      <AgendaCalendar.PreviousButton accessibilityLabel="Précédent" />
      <AgendaCalendar.TodayButton>Today</AgendaCalendar.TodayButton>
      <AgendaCalendar.NextButton accessibilityLabel="Suivant" />
    </AgendaCalendar.Nav>
  </AgendaCalendar.Header>
  {view === 'year' ? (
    <AgendaCalendar.YearPicker />
  ) : view === 'month' ? (
    <AgendaCalendar.MonthPicker />
  ) : (
    <>
      <AgendaCalendar.Weekdays />
      <AgendaCalendar.Week />
    </>
  )}
</AgendaCalendar>

view is a third piece of state, controlled the way value and week are. The pickers are rows in the strip's place, not a grid over it — they scroll sideways where the days were, the way the strip itself does. The walk is the Calendar's, one unit down: the title opens the years, a year opens that year's months, a month lands back on the week that holds it.

One pair of chevrons pages all three. PreviousButton / NextButton step whatever view is showing — a week, a month, or a year — and go dead when the unit they would reach has no selectable day in it. The caller reads view to render one of the three; the pickers only ever step it.

The title names the middle day's month

A week can straddle two. The fourth of seven is always in the majority month, and it is the only rule that does not call a week with six September days in it "August".

Cells of your own

AgendaCalendar.Week takes a function, exactly as Calendar.Grid does and for the same reason: seven cells are generated from a date rather than written.

<AgendaCalendar.Week>
  {date => (
    <AgendaCalendar.Day key={date.getTime()} date={date}>
      <Text>{date.getDate()}</Text>
    </AgendaCalendar.Day>
  )}
</AgendaCalendar.Week>

Extending it

useAgendaCalendar() is exported (R10) and carries the seven days, the chosen one, hasEvent, view / setView, and the moves — goByWeeks, goByMonths, goByYears, page (whichever the view shows), goToYear, goToMonthInYear, goToToday and select — plus isOnToday and isTodayResolved. Enough to write a strip of your own: names under the numbers, a count instead of a dot, a second mark in another colour. Outside an <AgendaCalendar> it throws by name.