Range Calendar
Vue date-timeCalendar variant for from/to date selection — click two dates and the range fills in between. Same min/max and disabled-date support as the single-date Calendar.
Also available for React ->Installation
$ 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.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/range-calendar
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
Dependencies
Used by
Files (13)
-
app/components/ui/range-calendar/RangeCalendar.vue 2.1 kB
<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> -
app/components/ui/range-calendar/RangeCalendarCell.vue 1 kB
<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> -
app/components/ui/range-calendar/RangeCalendarCellTrigger.vue 1.9 kB
<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' }), 'h-8 w-8 p-0 font-normal data-[selected]:opacity-100', '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground', // Selection Start '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', // Selection End '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', // 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> -
app/components/ui/range-calendar/RangeCalendarGrid.vue 0.7 kB
<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 space-x-1', props.class)" v-bind="forwardedProps" > <slot /> </RangeCalendarGrid> </template> -
app/components/ui/range-calendar/RangeCalendarGridBody.vue 0.3 kB
<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> -
app/components/ui/range-calendar/RangeCalendarGridHead.vue 0.3 kB
<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> -
app/components/ui/range-calendar/RangeCalendarGridRow.vue 0.7 kB
<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> -
app/components/ui/range-calendar/RangeCalendarHeadCell.vue 0.7 kB
<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> -
app/components/ui/range-calendar/RangeCalendarHeader.vue 0.7 kB
<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> -
app/components/ui/range-calendar/RangeCalendarHeading.vue 0.8 kB
<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> -
app/components/ui/range-calendar/RangeCalendarNextButton.vue 1 kB
<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> -
app/components/ui/range-calendar/RangeCalendarPrevButton.vue 1 kB
<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> -
app/components/ui/range-calendar/index.ts 0.9 kB
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