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 >] |
+--------------------------------------------------+| Element | Style |
|---|---|
| Container | Inline flex, vertically centered, gap 12px |
| Previous button | 32px square, rounded-lg, bg-inset, Lucide ChevronLeft 18px, text-2 |
| Next button | Same as previous, ChevronRight |
| Date text | 14px, font-semibold, text-1 |
| Date format | "Sabtu, 22 Maret 2026" (full Indonesian day + date) |
States
| State | Behavior |
|---|---|
| Default | Both arrows active |
| At today | Next button disabled (text-3, cursor-not-allowed), no future navigation |
| At earliest date | Previous button disabled |
| Loading | Date 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
| App | Context |
|---|---|
| store-admin | Daily journal, shift history, settlement history |
| ho-finance | Store daily report view |
| stock-app-web | Daily stock movement log |
Keyboard Shortcuts
| Key | Action |
|---|---|
Left Arrow | Go to previous day |
Right Arrow | Go to next day (if not today) |
T | Jump 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