Week Planner
Weekly schedule with time blocks: a typical week with optional odd and even weeks, or a dated week the user edits.
c2-week-planner shows a week: seven day columns on an hour scale, each event a block from its start to its end
time. By default it is a typical week, with no dates: an event names the weekday it happens on and repeats every week,
or, with alternate-weeks, only in odd or even weeks (weeks: "odd"), as school and shared-custody schedules do. Set
date to show a real calendar week with week-by-week navigation instead, and editable to let the user add, move and
resize events. Today’s
column is tinted (--c2-week-planner__day__today--background, drawn inside the grid lines) and carries a line at the
current time. Overlapping events cascade, each stepped in from the one before and drawn over it, so the earlier one’s
title stays readable, and the hour range (start-hour, end-hour, 8 to 18 by
default) grows to fit every event.
Import
import '@c2n/components/week-planner'The examples use icons from the separate @c2n/feather-icons package: npm install @c2n/feather-icons.
Usage
Kids’ schedule
Team week
Oct 5 – 11, 2026
Oct 5 – 11, 2026
Events
Each entry is { id?, title, day?, date?, start, end, weeks?, color?, textColor? }: day is mon to sun and
repeats every week, date (YYYY-MM-DD) is a single day shown only in a dated week, and start and
end are 24-hour HH:MM times (24:00 ends at midnight). An entry needs a day or a date; date wins when it has
both. Entries whose times are invalid or end before they start are skipped. color
is any CSS colour for that block (its text stays white in both themes; add textColor for a light color); otherwise
--c2-week-planner__event--background applies.
Heading
heading puts a title at the start of the header (--c2-week-planner__title--font-size,
--c2-week-planner__title--font-weight) and becomes the planner’s accessible name, unless aria-label is set. It is
called heading rather than title because title is the browser’s tooltip attribute, which would hover over the
whole planner.
For more than plain text, put your own markup in the heading slot: an icon next to the name, a link, or a field
that renames the planner. It replaces the heading text inside the same h2, and names the planner unless heading
or aria-label is set.
<c2-week-planner aria-label="Team week">
<c2-text-field slot="heading" value="Team week" aria-label="Planner name"></c2-text-field>
</c2-week-planner>Actions
The actions slot puts controls of your own at the end of the header, e.g. an “Add event” button or a settings menu,
spaced by --c2-week-planner__actions--gap. They sit before the previous/next day arrows on a narrow planner, and
the header shows for them even without a heading or the week switch. The planner leaves what they do to you.
<c2-week-planner heading="Team week">
<c2-button class="planner-add" slot="actions"><c2-feather-plus slot="prefix-icon"></c2-feather-plus>Add event</c2-button>
<c2-icon-button class="planner-icon-action" slot="actions" tooltip="Settings" aria-label="Settings"><c2-feather-settings></c2-feather-settings></c2-icon-button>
</c2-week-planner>Odd and even weeks
Alternate weeks are off by default: every entry shows each week and weeks is ignored. Add the alternate-weeks
attribute to turn them on. weeks is then "all" (the default), "odd" or "even", counted by ISO week number,
where week 1 holds the year’s first Thursday, and an Odd week | Even week switch appears above the grid: a
segmented c2-button-group, styled through its own --c2-button-group__* variables set on the planner. It opens on
the kind of the current week, whose button carries the current ISO week number in a c2-badge, and every-week
entries appear in both. Set parity="odd" or parity="even" to choose the view yourself; the switch fires parity-change with the kind
now shown. Today’s column is only marked while the switch shows the current kind of week.
Blocks are buttons: clicking one, or pressing Enter on it, fires event-click with the entry in event.detail.event.
Dated weeks
Set date to any day (YYYY-MM-DD) to show the calendar week holding it, starting on week-start or the language’s
first day. The day headers then carry the day of the month (--c2-week-planner__day-number--font-weight), the header
shows the week’s range (--c2-week-planner__week-title--color, --c2-week-planner__week-title--font-size) and
previous week / next week arrows with a Today button, and today’s column is only marked in the week that holds it.
The navigation updates date and fires week-change with the first and last day shown, { start, end } as
YYYY-MM-DD — the moment to load that week’s entries. It does not bubble: listen on the planner itself.
Entries with a date show in their own week only; entries with a day keep repeating every week, so a dated plan
can mix one-off appointments with a routine. With alternate-weeks, weeks follows the ISO number of the week shown
and there is no switch. Without date the planner stays the dateless typical week, and entries with a date are left
out of it.
Editing
editable lets the user arrange the plan. The planner never changes events itself: it reports what the user asked
for, and you update events.
- Add. Clicking an empty spot fires
slot-clickwith{ day, date?, start, end }: the time under the pointer, snapped down tosnap-minutes(30 by default), and an hour-long range, capped at24:00. The hour under the pointer is tinted (--c2-week-planner__slot__hover--background). From the keyboard, each day column starts with a “New event” button, invisible until it has focus, at the column’s first free hour; Enter fires the same event. - Move and resize. Drag a block to another time or day, or drag its bottom edge
(
--c2-week-planner__resize-handle--height) to change its end. A dashed preview (--c2-week-planner__preview--*) shows where it lands while the block itself fades (--c2-week-planner__event__dragging--opacity); Escape cancels. The drop firesevent-changewith{ event, changes }:eventis the entry fromeventsandchangesits newstart,endand, for adayentry,day, or, for adateentry,date, so spreadingchangesonto the entry applies it. A press that does not travel a few pixels is still a click (event-click). - Keyboard. On a focused block, Alt+Arrow Up/Down moves it by
snap-minutes, Alt+Arrow Left/Right by a day (adayentry wraps around the week), and Alt+Shift+Arrow Up/Down changes its end; each firesevent-change, and the block keeps focus once you have applied it.
import '@c2n/components/week-planner'
import type { WeekPlannerEvent } from '@c2n/components/week-planner'
const planner = document.querySelector('c2-week-planner')!
planner.date = '2026-10-07'
planner.editable = true
// loadWeek: your own fetch of that week's entries.
planner.addEventListener('week-change', ({ detail }) => loadWeek(detail.start, detail.end))
planner.addEventListener('slot-click', ({ detail }) => {
const entry: WeekPlannerEvent = { id: crypto.randomUUID(), title: 'New task', date: detail.date, start: detail.start, end: detail.end }
planner.events = [...planner.events, entry]
})
planner.addEventListener('event-change', ({ detail }) => {
planner.events = planner.events.map((entry) => (entry === detail.event ? { ...entry, ...detail.changes } : entry))
})Narrow screens
Below 640px of width the planner shows three days, and below 420px a single day, of a strip that scrolls sideways and snaps to a day: swipe it on a touch screen, or page through the week with the previous/next arrows that appear in its header. It opens on today and the hour labels stay in place. The breakpoints follow the planner’s own width, not the viewport’s, so a planner in a narrow sidebar switches too.
Team week
Team week
In a layout that sizes it from its content, such as a centred flex column, the planner takes 720px or the space
available, whichever is smaller; give it a width to choose another.
Language
The planner’s text follows locale (fr, en-GB, …) and, when it is not set, the browser’s language. The day
columns start on that language’s usual first day (Sunday in en-US, Monday in fr or en-GB, Saturday in
ar-EG); week-start (monday, sunday or saturday) overrides it. Day names,
“today” and “this week” come from the browser’s Intl data, so they exist in every language; the switch labels
(Odd week, Even week) and the accessible names are built in for English, French, German, Spanish, Italian, Portuguese,
Dutch and Vietnamese, and fall back to English otherwise. A dated week formats its dates and range through Intl too.
import '@c2n/components/week-planner'
const planner = document.querySelector('c2-week-planner')!
planner.events = [{ id: 'pickup', title: 'School pick-up', day: 'wed', start: '13:30', end: '17:00', weeks: 'even' }]
planner.addEventListener('event-click', (event) => console.log(event.detail.event.id))