Skip to content
weftGitHub

WeekPicker

Pick a whole week from a calendar.

SourcePackage @weft-js/warp

Preview

tsx
import { state } from '@weft-js/core'import { Stack, Text, WeekPicker } from '@weft-js/warp'import type { IsoDate, IsoWeek } from '@weft-js/warp'export function Sprint() {  const week = state<IsoWeek | null>('2026-W45')  return (    <Stack gap="related">      <WeekPicker label="Sprint week" today={TODAY} value={week} />      <Text subtle role="caption">Saved as {week.value ?? 'nothing'}</Text>    </Stack>  )}const TODAY: IsoDate = '2026-11-02'

Installation

npm install @weft-js/warp

Usage

tsx
import { WeekPicker } from '@weft-js/warp'

Local week numbers

numbering="locale" follows the local first day of the week.

tsx
import { WeekPicker } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function Locale() {  return (    <WeekPicker label="Payroll week" today={TODAY} defaultValue="2026-W45" numbering="locale" />  )}const TODAY: IsoDate = '2026-11-02'

Hint, error, read-only, disabled

The same hint, error, readonly and disabled as every field.

tsx
import { Stack, Text, WeekPicker, Grid } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function States() {  return (    <Grid cols={2} gap="group">      <Cell name="Empty"><WeekPicker label="Week" today={TODAY} /></Cell>      <Cell name="Hint"><WeekPicker label="Week" today={TODAY} hint="Monday to Sunday." /></Cell>      <Cell name="Invalid"><WeekPicker label="Week" today={TODAY} error="Choose the week you worked." /></Cell>      <Cell name="Read-only"><WeekPicker label="Week" today={TODAY} defaultValue="2026-W44" readonly /></Cell>      <Cell name="Disabled"><WeekPicker label="Week" today={TODAY} defaultValue="2026-W44" disabled /></Cell>    </Grid>  )}const TODAY: IsoDate = '2026-11-02'const Cell = (p: { name: string; children?: unknown }) => (  <Stack gap="tight">    <Text role="caption" subtle>{p.name}</Text>    {p.children}  </Stack>)

A timesheet

The week gives the dates of its days. An ISO week starts on the Monday.

tsx
import { state } from '@weft-js/core'import { Row, Stack, Text, WeekPicker } from '@weft-js/warp'import type { IsoDate, IsoWeek } from '@weft-js/warp'export function Timesheet() {  const week = state<IsoWeek | null>('2026-W44')  const day = (i: number) => new Date(mondayOf(week.value!).getTime() + i * 86400000).toISOString().slice(5, 10)  return (    <Stack gap="group">      <WeekPicker label="Timesheet week" today={TODAY} value={week} />      {week.value ? (        <Stack gap="tight" divided>          {DAYS.map((d, i) => (            <Row key={d} justify="between"><Text>{d} {day(i)}</Text><Text subtle>{HOURS[i]} h</Text></Row>          ))}          <Row justify="between"><Text role="label">Total</Text><Text role="label">{HOURS.reduce((a, b) => a + b, 0)} h</Text></Row>        </Stack>      ) : <Text subtle>Pick a week to fill in its hours.</Text>}    </Stack>  )}const TODAY: IsoDate = '2026-11-02'function mondayOf(week: string) {  const [year, n] = [+week.slice(0, 4), +week.slice(6)]  const jan4 = new Date(Date.UTC(year, 0, 4))  return new Date(jan4.getTime() + ((n - 1) * 7 - ((jan4.getUTCDay() + 6) % 7)) * 86400000)}const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']const HOURS = [7.5, 8, 6, 8.5, 4]

When to use

Use it for

  • Timesheets, sprints and rotas that run by the week

Reach for something else

Accessibility

Arrows
Move through the days
EnterorSpace
Picks that day's week

API Reference

PropTypeDefaultDescription
labelstring—The accessible name, drawn as the floating border label (or above, classic). Required.
hintunknown—One line of help under the control.
errorunknown—Shown instead of the hint; marks the field invalid.
iconIconRef—Leading icon: it sweeps with the border.
prefixunknown—Text or an element inside the field before the value, such as $.
suffixunknown—Text or an element inside the field after the value, such as kg.
variant"floating" | "classic"—floating (default, or the app's warp({ fields })) · classic: the label above.
appearance"line"—line: only a bottom line under the value, no box or fill. The boxed field otherwise.
size"sm" | "md" | "lg"'md'Control size. md by default.
clearablebooleanfalseA clear button (shown once there is a value).
counternumber—Maximum length, shown as a counter.
idstring—The element id. One is made for you if you leave it out.
disabledbooleanfalseTurns it off, and takes it out of the tab order.
readonlybooleanfalseShows the value and lets it be selected and copied, but not changed.
requiredbooleanfalseThe form will not submit without it.
namestring—The name it posts under in a form.
localestring—The language and region used to format values.
todaystring—Pins "today" (server render, tests); the browser's clock otherwise.
presentationPresentation—auto opens a popover, or a sheet when the viewport is under 600px wide; popover and sheet force one.
value`${number}-W${number}` | State<`${number}-W${number}` | null> | null—The current value, when you own the state.
defaultValue`${number}-W${number}` | null—The starting value, when the component owns the state.
openboolean | State<boolean>—A state box that holds whether it is open.
defaultOpenbooleanfalseStarts open, when the component keeps its own open state.
numbering"iso" | "locale"'iso'The day each grid row starts on: iso (the default) starts on Monday, so a row is one ISO 8601 week; locale starts on the locale's first day of the week. The value is an ISO week either way.
EventTypeDefaultDescription
onInput((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen…—Called on each keystroke.
onValueChange((v: `${number}-W${number}` | null, reason: ChangeReason) => void)—Called with each new value, and whether a pointer, a key or code made the change.
onOpenChange((o: boolean) => void)—Called with true when it opens and false when it closes.

RelatedMonth pickerPeriod picker