# Year over Year Range

> A day range that also shows the same dates a year earlier, moving 29 February to 28 February in non-leap years and flagging the day count mismatch. Use this when figures are read against the same dates last year and leap years must not skew the comparison.

A shadcn/ui block from the [@tealmac](https://ui.tealmac.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/tealmac/year-over-year-range).

- Page, with a live preview: https://shadcn.exchange/r/tealmac/year-over-year-range
- Screenshot: https://shadcn.exchange/shot/tealmac/year-over-year-range/light.png
- Registry item: https://ui.tealmac.com/r/year-over-year-range.json
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/tealmac/year-over-year-range.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@tealmac/date-range-field`, `@tealmac/date-time-core`

## Files

### `registry/patterns/year-over-year-range/year-over-year-range.tsx`

```tsx
"use client"

import * as React from "react"

import { DateRangeField } from "@/components/ui/date-range-field"
import {
  formatDateRange,
  rangeLength,
  resolveLocale,
  samePeriodLastYear,
  type DateRange,
} from "@/components/ui/date-time-core"

export interface YearOverYearRangeProps {
  value?: DateRange | null
  defaultValue?: DateRange | null
  onValueChange?: (range: DateRange | null, lastYear: DateRange | null) => void
  max?: string
  locale?: string
}

export function yearOverYear(range: DateRange) {
  const lastYear = samePeriodLastYear(range)
  return { lastYear, dayDifference: rangeLength(range) - rangeLength(lastYear) }
}

export function YearOverYearRange({
  value,
  defaultValue = null,
  onValueChange,
  max,
  locale,
}: YearOverYearRangeProps) {
  const loc = resolveLocale(locale)
  const [inner, setInner] = React.useState<DateRange | null>(defaultValue)
  const range = value === undefined ? inner : value
  const result = range ? yearOverYear(range) : null

  return (
    <div className="flex flex-col gap-3">
      <DateRangeField
        locale={loc}
        max={max}
        value={range}
        onValueChange={(next) => {
          if (value === undefined) setInner(next)
          onValueChange?.(next, next ? yearOverYear(next).lastYear : null)
        }}
      />
      <div role="status" className="text-sm">
        {range && result ? (
          <>
            <p>
              <strong>This year</strong>{" "}
              {formatDateRange(range.from, range.to, loc)}
            </p>
            <p>
              <strong>Last year</strong>{" "}
              {formatDateRange(result.lastYear.from, result.lastYear.to, loc)}
            </p>
            {result.dayDifference !== 0 ? (
              <p className="text-muted-foreground">
                This year has {Math.abs(result.dayDifference)}{" "}
                {result.dayDifference > 0 ? "more" : "fewer"} day
                {Math.abs(result.dayDifference) === 1 ? "" : "s"} because of the
                leap day.
              </p>
            ) : null}
          </>
        ) : (
          "Enter a date range."
        )}
      </div>
    </div>
  )
}
```
