Skip to content

Date Navigation

The date navigation pattern allows users to browse historical data one day at a time. It provides previous/next buttons flanking a date display.

Sabtu, 22 Maret 2026

Anatomy

+--------------------------------------------------+
|  [< Prev]    Sabtu, 22 Maret 2026    [Next >]    |
+--------------------------------------------------+
ElementStyle
ContainerInline flex, vertically centered, gap 12px
Previous button32px square, rounded-lg, bg-inset, Lucide ChevronLeft 18px, text-2
Next buttonSame as previous, ChevronRight
Date text14px, font-semibold, text-1
Date format"Sabtu, 22 Maret 2026" (full Indonesian day + date)

States

StateBehavior
DefaultBoth arrows active
At todayNext button disabled (text-3, cursor-not-allowed), no future navigation
At earliest datePrevious button disabled
LoadingDate text shows subtle pulse animation while data loads

Short Format

For compact layouts, use abbreviated format:

[<]  22 Mar 2026  [>]
  • Date: font-mono, 13px, text-1
  • Buttons: 28px square

React Component

tsx
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { format, addDays, subDays, isToday, isBefore } from 'date-fns'
import { id } from 'date-fns/locale'

interface DateNavigationProps {
  date: Date
  onChange: (date: Date) => void
  minDate?: Date
  compact?: boolean
}

export function DateNavigation({ date, onChange, minDate, compact = false }: DateNavigationProps) {
  const atToday = isToday(date)
  const atMin = minDate ? isBefore(date, addDays(minDate, 1)) : false

  const dateText = compact
    ? format(date, 'dd MMM yyyy', { locale: id })
    : format(date, 'EEEE, dd MMMM yyyy', { locale: id })

  const btnSize = compact ? 'w-7 h-7' : 'w-8 h-8'
  const iconSize = compact ? 'w-4 h-4' : 'w-[18px] h-[18px]'
  const textSize = compact ? 'font-mono text-[13px]' : 'text-sm font-semibold'

  return (
    <div className="inline-flex items-center gap-3">
      <button
        onClick={() => onChange(subDays(date, 1))}
        disabled={atMin}
        className={cn(
          btnSize, 'rounded-lg bg-inset flex items-center justify-center transition-colors',
          atMin ? 'text-3 cursor-not-allowed' : 'text-2 hover:bg-hover'
        )}
      >
        <ChevronLeft className={iconSize} />
      </button>

      <span className={cn(textSize, 'text-1 select-none min-w-[180px] text-center')}>
        {dateText}
      </span>

      <button
        onClick={() => onChange(addDays(date, 1))}
        disabled={atToday}
        className={cn(
          btnSize, 'rounded-lg bg-inset flex items-center justify-center transition-colors',
          atToday ? 'text-3 cursor-not-allowed' : 'text-2 hover:bg-hover'
        )}
      >
        <ChevronRight className={iconSize} />
      </button>
    </div>
  )
}

Usage

AppContext
store-adminDaily journal, shift history, settlement history
ho-financeStore daily report view
stock-app-webDaily stock movement log

Keyboard Shortcuts

KeyAction
Left ArrowGo to previous day
Right ArrowGo to next day (if not today)
TJump to today

Loading pattern

When navigating dates, show a skeleton loader on the page content while keeping the date navigation interactive. This lets users quickly skip through multiple days.

Don't

  • Don't allow navigation to future dates unless explicitly needed (e.g., scheduling)
  • Don't use a calendar picker for single-day sequential browsing --- the chevron pattern is faster
  • Don't reset scroll position when changing dates --- maintain the user's viewport

RetailOS - Sistem ERP Retail Modern untuk Indonesia