Week strip

Free · MITReact Native · Expo

A horizontal week of days: one accent pill slides between days and stretches toward the new one, weeks page with the finger 1:1, dots mark busy days, and a header shows the month with a Today button.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/week-strip.json

Then run npx expo install --fix so react-native-gesture-handler, react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/week-strip.

Usage

import { useState } from "react";
import { WeekStrip } from "@/components/goodui-native/week-strip/week-strip";

export function Schedule({ events }: { events: { day: string }[] }) {
  const [day, setDay] = useState(new Date());
  return (
    <WeekStrip
      value={day}
      onValueChange={setDay}
      min={new Date()}
      markedDates={(d) => events.filter((e) => e.day === d.toDateString()).length}
    />
  );
}

When to use

Use it for
  • Switching the day shown below it: a schedule, workout plan, class timetable, bookings for a day.
  • Picking a day close to today in a booking flow, above time-slots.
Reach for something else
  • Dates weeks or months away, or ranges: use date-picker.
  • Switching between views that are not days: use segmented-control or tab-bar.

Examples

Follow the weekday while swiping, no header

<WeekStrip showHeader={false} selectOnSwipe defaultValue={new Date()} onValueChange={loadWorkout} />

French, Monday first, closed on Sundays

<WeekStrip locale="fr-FR" todayLabel="Aujourd'hui" isDateDisabled={(d) => d.getDay() === 0} value={day} onValueChange={setDay} />

API reference

PropTypeDefault
value
Selected day (controlled).
Date—
defaultValue
Starting day when uncontrolled. Defaults to today.
Date—
onValueChange
Called with the chosen day (midnight local).
(date: Date) => void—
markedDates
Dots under a day: `true` for one, a number for up to three (for example how many events).
(date: Date) => boolean | number—
min
Earliest day. Earlier days are dimmed and the strip does not page before its week.
Date—
max
Latest day. Later days are dimmed and the strip does not page past its week.
Date—
isDateDisabled
Days that cannot be chosen, for example days the venue is closed.
(date: Date) => boolean—
locale
BCP 47 locale for weekday and month names, digits and the first day of the week. Defaults to the device locale.
string—
weekStartsOn
First column: 0 Sunday to 6 Saturday. Defaults to the locale's first day of the week.
0 | 1 | 2 | 3 | 4 | 5 | 6—
showHeader
Month title, a Today button when today is not selected, and previous and next week arrows. Default `true`.
booleantrue
todayLabel
Label of the Today button. Same as `labels.today`.
string—
selectOnSwipe
Swiping to another week also moves the selection to the same weekday there. Default `false`.
booleanfalse
onWeekChange
Called with the first day of the week after paging.
(weekStart: Date) => void—
haptic
`false` silences the select haptic on a day change.
booleantrue
accessibilityLabel
Names the strip for screen readers. Same as `labels.days`.
string—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<WeekStripLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Selecting: the pill's edge facing the new day leads on the snappy spring and the trailing edge follows on smooth, so it stretches toward the day and settles. Each label and dot turns to the on-accent color by how much the pill covers it.
  • Paging: weeks and the pill follow a horizontal drag 1:1, with resistance before min and past max; release pages one week by position and velocity on the smooth spring. The arrows and Today page on the same spring; a far jump lands one week short and pages in.
  • Reduced motion: the pill and paging move with a short timing instead of springs.
  • Haptic: select when the selected day changes (tap, keys, Today, or a swipe with selectOnSwipe)
  • Haptic: haptic={false} silences it
  • Three weeks are mounted at a time. The pill, paging and label colors run on the UI thread; React renders when the week or the day changes.

Accessibility

  • The strip is a tablist named by accessibilityLabel (default "Days"); each day is a tab with selected and disabled in accessibilityState, and aria-selected on the web.
  • A day's label starts with its visible weekday and number, then the full date and today, unavailable or the number of events.
  • On the web the selected day is the one focusable tab: Left and Right move the selection across weeks, Home and End to the ends of the week, skipping unavailable days.
  • The header has Previous week and Next week icon buttons, so paging never depends on a swipe, and a Today button while another day is selected.
  • Weeks off screen are hidden from screen readers. The strip is 68 pt tall and each day a seventh of the width.

Notes for AI

  • value is a Date; onValueChange gets a Date at local midnight. Compare days with toDateString().
  • markedDates returns true for one dot or a number for up to three.
  • Weekday and month names and digits come from Intl with locale; the first weekday follows the locale unless weekStartsOn is set.
  • Paging alone does not change the selection unless selectOnSwipe is set; use onWeekChange to load a week's data.