FOLIO // NEXTUI

Calendar

Interactive month-view calendar with previous/next navigation and day selection. Highlights today in forest green and the selected day in cognac.

$npx @voltenworks/shipui add calendar --theme folio
Or install the base component for free:
Live Preview
Open full demo
voltenworks.com/shipui/folio/demo/components#06-date
Usage
TSX
<Calendar initialDate={new Date()} />
Source
TSX
'use client'

import { useState } from 'react'

const MONTHS = [
  'January','February','March','April','May','June',
  'July','August','September','October','November','December',
]
const DAY_NAMES = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']

function daysInMonth(year: number, month: number): number {
  return new Date(year, month + 1, 0).getDate()
}

function firstWeekdayOffset(year: number, month: number): number {
  const d = new Date(year, month, 1).getDay()
  return d === 0 ? 6 : d - 1
}

interface CalendarProps {
  initialDate?: Date
}

type Cell = { day: number; type: 'prev' | 'current' | 'next' }

export function Calendar({ initialDate }: CalendarProps): React.JSX.Element {
  const today   = new Date()
  const init    = initialDate ?? today

  const [year,     setYear]     = useState(init.getFullYear())
  const [month,    setMonth]    = useState(init.getMonth())
  const [selected, setSelected] = useState<number | null>(null)

  function prevMonth() {
    if (month === 0) { setYear(y => y - 1); setMonth(11) }
    else setMonth(m => m - 1)
    setSelected(null)
  }

  function nextMonth() {
    if (month === 11) { setYear(y => y + 1); setMonth(0) }
    else setMonth(m => m + 1)
    setSelected(null)
  }

  const totalDays    = daysInMonth(year, month)
  const offset       = firstWeekdayOffset(year, month)
  const prevTotal    = daysInMonth(year, month - 1)

  const cells: Cell[] = []
  for (let i = offset - 1; i >= 0; i--)    cells.push({ day: prevTotal - i, type: 'prev'    })
  for (let d = 1; d <= totalDays; d++)      cells.push({ day: d,             type: 'current' })
  const remaining = 42 - cells.length
  for (let d = 1; d <= remaining; d++)      cells.push({ day: d,             type: 'next'    })

  const isToday = (cell: Cell) =>
    cell.type === 'current' &&
    cell.day  === today.getDate() &&
    month     === today.getMonth() &&
    year      === today.getFullYear()

  return (
    <div className="folio-calendar">
      <div className="folio-calendar-header">
        <button type="button" className="folio-calendar-nav-btn" onClick={prevMonth} aria-label="Previous month">
          &#8592;
        </button>
        <span className="folio-calendar-month-label">
          {MONTHS[month]} {year}
        </span>
        <button type="button" className="folio-calendar-nav-btn" onClick={nextMonth} aria-label="Next month">
          &#8594;
        </button>
      </div>

      <div className="folio-calendar-grid">
        {DAY_NAMES.map(d => (
          <div key={d} className="folio-calendar-day-name">{d}</div>
        ))}

        {cells.map((cell, i) => {
          const cls = [
            'folio-calendar-cell',
            cell.type !== 'current'                         ? 'other-month' : '',
            isToday(cell)                                   ? 'today'       : '',
            cell.type === 'current' && selected === cell.day ? 'selected'   : '',
          ].filter(Boolean).join(' ')

          return (
            <div
              key={i}
              className={cls}
              role={cell.type === 'current' ? 'button' : undefined}
              tabIndex={cell.type === 'current' ? 0 : -1}
              onClick={() => cell.type === 'current' && setSelected(cell.day)}
              onKeyDown={e => {
                if (e.key === 'Enter' && cell.type === 'current') setSelected(cell.day)
              }}
            >
              {cell.day}
            </div>
          )
        })}
      </div>
    </div>
  )
}
Preview in theme demoGet full theme, $29
Works withNext.js 15React 19Tailwind v4TypeScript 5
More from FOLIO // NEXT
FOLIO // NEXTUI

Button

Multi-variant button with primary, secondary, and ghost styles in three sizes. Renders as a Next.js Link when an href is provided.

FOLIO // NEXTUI

Badge

Inline label badge with five variants: ink, accent, cta, ghost, and parchment. Suited for tags, status labels, and category markers.

FOLIO // NEXTUI

Card

Composable card container with sharp zero-radius corners in the FOLIO aesthetic. Pairs with folio-card-header, folio-card-body, and folio-card-footer CSS classes.

FOLIO // NEXTUI

DateBlock

Magazine-style date display that renders month, day number, and weekday name in an editorial dateline layout. Server component.

FOLIO // NEXTUI

Pullquote

Editorial blockquote with ruled top and bottom borders and an optional attribution citation. Suited for testimonials and highlighted copy.

FOLIO // NEXTUI

Text

Polymorphic text primitive with body, caption, label, and code variants. Renders as any inline or block HTML tag via the `as` prop.

FOLIO // NEXTSection

Hero

Three-column editorial hero with a 44px spine, a full-width copy column, and a 380px table-of-contents sidebar. Includes a scrolling ticker strip and collapses to a single column on mobile.

FOLIO // NEXTSection

Features

Ledger-row feature list using a 56px index gutter, feature title, description, and a right-aligned tag. Roman numeral indexes reinforce the editorial aesthetic.

FOLIO // NEXTSection

About

Two-column about section with forest green background, a pullquote, and body copy. Pairs a narrative text column with a supporting details column.