
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
range-calendaruiInline from→to calendar grid. Click two dates; the range fills between. For a form field with a trigger, use Date Picker type="range" instead. Same min/max and disabled-date support as Calendar.
Also available for React ->Calendar is the always-visible month grid. Date Picker is the form field with popover. Range Calendar manages multi-month spans, and Event Calendar is the multi-view scheduling engine.
| Component | Role | Use when |
|---|---|---|
| Calendar Embedded dashboard views or scheduling pages where the month grid stays continuously visible. | Grid Primitive | |
| RangeCalendarcurrent Contiguous multi-month booking ranges, check-in / check-out spans, and date windows. | Range Primitive | |
| DatePicker Forms, filter bars, and dialogs where space is tight and the calendar opens inside a popover. | Form Control | |
| EventCalendar Full calendar interfaces with month/week/day views, timed hourly intervals, and overlap collision lanes. | Scheduling Engine |
$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/range-calendar.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/range-calendar.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/range-calendar.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/range-calendar.jsonnpx shadcn-vue@latest add @uipkge/range-calendarInstalls to:app/components/ui/range-calendar/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
<script lang="ts" setup>
import type { RangeCalendarRootEmits, RangeCalendarRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarRoot, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils'
import {
RangeCalendarCell,
RangeCalendarCellTrigger,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHead,
RangeCalendarGridRow,
RangeCalendarHeadCell,
RangeCalendarHeader,
RangeCalendarHeading,
RangeCalendarNextButton,
RangeCalendarPrevButton,
} from '.'
const props = defineProps<RangeCalendarRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<RangeCalendarRootEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<RangeCalendarRoot
v-slot="{ grid, weekDays }"
data-uipkge
data-slot="range-calendar"
:class="cn('p-3', props.class)"
v-bind="forwarded"
>
<RangeCalendarHeader>
<RangeCalendarHeading />
<div class="flex items-center gap-1">
<RangeCalendarPrevButton />
<RangeCalendarNextButton />
</div>
</RangeCalendarHeader>
<div class="mt-4 flex flex-col gap-y-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full">
<RangeCalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
<RangeCalendarCellTrigger :day="weekDate" :month="month.value" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
</RangeCalendarRoot>
</template>
<script lang="ts" setup>
import type { RangeCalendarCellProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarCell, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarCellProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarCell
data-uipkge
data-slot="range-calendar-cell"
:class="
cn(
'[&:has([data-selected])]:bg-accent [&:has([data-highlighted])]:bg-accent relative p-0 text-center text-sm focus-within:relative focus-within:z-20 first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md',
props.class,
)
"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarCell>
</template>
<script lang="ts" setup>
import type { RangeCalendarCellTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarCellTrigger, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
const props = withDefaults(defineProps<RangeCalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>(), {
as: 'button',
})
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarCellTrigger
data-uipkge
data-slot="range-calendar-trigger"
:class="
cn(
buttonVariants({ variant: 'ghost' }),
'size-9 cursor-pointer p-0 font-normal transition-[color,background-color,transform,box-shadow] duration-150 data-[selected]:opacity-100',
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
// Selection Start / End — subtle pop
'data-[selection-start]:bg-primary data-[selection-start]:text-primary-foreground data-[selection-start]:hover:bg-primary data-[selection-start]:hover:text-primary-foreground data-[selection-start]:focus:bg-primary data-[selection-start]:focus:text-primary-foreground motion-safe:data-[selection-start]:animate-[calendar-day-pop_220ms_cubic-bezier(0.22,1.4,0.36,1)_both]',
'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground motion-safe:data-[selection-end]:animate-[calendar-day-pop_220ms_cubic-bezier(0.22,1.4,0.36,1)_both]',
// Outside months
'data-[outside-view]:text-muted-foreground',
// Disabled
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
// Unavailable
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
props.class,
)
"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarCellTrigger>
</template>
<style>
@keyframes calendar-day-pop {
0% {
transform: scale(0.86);
}
70% {
transform: scale(1.06);
}
100% {
transform: scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
[data-slot='range-calendar-trigger'] {
animation: none !important;
transition: none !important;
}
}
</style>
<script lang="ts" setup>
import type { RangeCalendarGridProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarGrid, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarGridProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarGrid
data-uipkge
data-slot="range-calendar-grid"
:class="cn('w-full border-collapse', props.class)"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarGrid>
</template>
<script lang="ts" setup>
import type { RangeCalendarGridBodyProps } from 'reka-ui'
import { RangeCalendarGridBody } from 'reka-ui'
const props = defineProps<RangeCalendarGridBodyProps>()
</script>
<template>
<RangeCalendarGridBody data-uipkge data-slot="range-calendar-grid-body" v-bind="props">
<slot />
</RangeCalendarGridBody>
</template>
<script lang="ts" setup>
import type { RangeCalendarGridHeadProps } from 'reka-ui'
import { RangeCalendarGridHead } from 'reka-ui'
const props = defineProps<RangeCalendarGridHeadProps>()
</script>
<template>
<RangeCalendarGridHead data-uipkge data-slot="range-calendar-grid-head" v-bind="props">
<slot />
</RangeCalendarGridHead>
</template>
<script lang="ts" setup>
import type { RangeCalendarGridRowProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarGridRow, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarGridRow
data-uipkge
data-slot="range-calendar-grid-row"
:class="cn('flex', props.class)"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarGridRow>
</template>
<script lang="ts" setup>
import type { RangeCalendarHeadCellProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarHeadCell, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarHeadCell
data-uipkge
data-slot="range-calendar-head-cell"
:class="cn('text-muted-foreground w-8 rounded-md text-[0.8rem] font-normal', props.class)"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarHeadCell>
</template>
<script lang="ts" setup>
import type { RangeCalendarHeaderProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarHeader, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarHeader
data-uipkge
data-slot="range-calendar-header"
:class="cn('relative flex w-full items-center justify-center pt-1', props.class)"
v-bind="forwardedProps"
>
<slot />
</RangeCalendarHeader>
</template>
<script lang="ts" setup>
import type { RangeCalendarHeadingProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RangeCalendarHeading, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<RangeCalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
defineSlots<{
default: (props: { headingValue: string }) => any
}>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarHeading
v-slot="{ headingValue }"
data-uipkge
data-slot="range-calendar-heading"
:class="cn('text-sm font-medium', props.class)"
v-bind="forwardedProps"
>
<slot :heading-value>
{{ headingValue }}
</slot>
</RangeCalendarHeading>
</template>
<script lang="ts" setup>
import type { RangeCalendarNextProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ChevronRight } from 'lucide-vue-next'
import { RangeCalendarNext, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
const props = defineProps<RangeCalendarNextProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarNext
data-uipkge
data-slot="range-calendar-next-button"
:class="
cn(
buttonVariants({ variant: 'outline' }),
'absolute right-1',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class,
)
"
v-bind="forwardedProps"
>
<slot>
<ChevronRight class="size-4" aria-hidden="true" />
</slot>
</RangeCalendarNext>
</template>
<script lang="ts" setup>
import type { RangeCalendarPrevProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ChevronLeft } from 'lucide-vue-next'
import { RangeCalendarPrev, useForwardProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
const props = defineProps<RangeCalendarPrevProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<RangeCalendarPrev
data-uipkge
data-slot="range-calendar-prev-button"
:class="
cn(
buttonVariants({ variant: 'outline' }),
'absolute left-1',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class,
)
"
v-bind="forwardedProps"
>
<slot>
<ChevronLeft class="size-4" aria-hidden="true" />
</slot>
</RangeCalendarPrev>
</template>
export { default as RangeCalendar } from './RangeCalendar.vue'
export { default as RangeCalendarCell } from './RangeCalendarCell.vue'
export { default as RangeCalendarCellTrigger } from './RangeCalendarCellTrigger.vue'
export { default as RangeCalendarGrid } from './RangeCalendarGrid.vue'
export { default as RangeCalendarGridBody } from './RangeCalendarGridBody.vue'
export { default as RangeCalendarGridHead } from './RangeCalendarGridHead.vue'
export { default as RangeCalendarGridRow } from './RangeCalendarGridRow.vue'
export { default as RangeCalendarHeadCell } from './RangeCalendarHeadCell.vue'
export { default as RangeCalendarHeader } from './RangeCalendarHeader.vue'
export { default as RangeCalendarHeading } from './RangeCalendarHeading.vue'
export { default as RangeCalendarNextButton } from './RangeCalendarNextButton.vue'
export { default as RangeCalendarPrevButton } from './RangeCalendarPrevButton.vue'
Raw manifest:https://uipkge.dev/r/vue/range-calendar.json