Vitral 0.2
Formulário

DateRange

Duas datas e os dias entre elas, escolhidas em dois meses lado a lado ou em um, com o intervalo desenhado antes de ser confirmado.

Importação

main.ts
import { DateRange } from '@vitral/vue';

Dois meses

O padrão. Escolha um início e depois um fim; a faixa entre eles segue o ponteiro antes da confirmação. Cada calendário para no próprio mês — os dias de um lado e do outro ficam em branco, em vez de repetir o mês vizinho.

Your stay
6 nights
<script setup lang="ts">import { DateRange, type DateRangeValue, StackPanel } from '@vitral/vue';import { computed, ref } from 'vue'; const at = (offset: number) => {    const d = new Date();    d.setDate(d.getDate() + offset);    d.setHours(0, 0, 0, 0);    return d;}; const stay = ref<DateRangeValue>({ start: at(3), end: at(9) });const nights = computed(() => (stay.value.start && stay.value.end ? Math.round((stay.value.end.getTime() - stay.value.start.getTime()) / 86400000) : 0));</script> <template>    <StackPanel spacing="0.375rem">        <span id="dr-stay">Your stay</span>        <DateRange v-model="stay" :min-date="at(0)" show-clear-button aria-labelledby="dr-stay" />        <small style="color: var(--vt-text-muted-color)">{{ nights ? `${nights} night${nights === 1 ? '' : 's'}` : 'No dates yet' }}</small>    </StackPanel></template>

Um mês

O mesmo intervalo num único calendário, para um formulário sem espaço para dois. Sem vizinho para repetir, os dias de um lado e do outro do mês voltam.

Dates
Empty
<script setup lang="ts">import { DateRange, type DateRangeValue, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const single = ref<DateRangeValue>({ start: null, end: null });</script> <template>    <StackPanel spacing="0.375rem">        <span id="dr-one">Dates</span>        <DateRange v-model="single" :months="1" placeholder="Pick two dates" aria-labelledby="dr-one" />        <small style="color: var(--vt-text-muted-color)">{{ single.start ? `${single.start.toLocaleDateString()} → ${single.end?.toLocaleDateString() ?? '…'}` : 'Empty' }}</small>    </StackPanel></template>

Com os intervalos que as pessoas pedem pelo nome

O rodapé é um slot: presets, uma contagem de noites, o que o formulário precisar.

<script setup lang="ts">import { Button, DateRange, type DateRangeValue, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const at = (offset: number) => {    const d = new Date();    d.setDate(d.getDate() + offset);    d.setHours(0, 0, 0, 0);    return d;}; const report = ref<DateRangeValue>({ start: at(-30), end: at(0) }); // A range picker is usually next to a few spans people ask for by name.const presets = [    { label: 'Last 7 days', days: 7 },    { label: 'Last 30 days', days: 30 },    { label: 'Last 90 days', days: 90 }];const usePreset = (days: number) => (report.value = { start: at(-days + 1), end: at(0) });</script> <template>    <DateRange v-model="report" :max-date="at(0)" aria-label="Reporting period">        <template #footer="{ nights: n, clear }">            <StackPanel orientation="horizontal" spacing="0.375rem" align="center" wrap as="span">                <Button v-for="p in presets" :key="p.days" :label="p.label" severity="secondary" variant="text" size="small" @click="usePreset(p.days)" />            </StackPanel>            <small style="color: var(--vt-text-muted-color)">{{ n + 1 }} days · <a href="#" @click.prevent="clear">clear</a></small>        </template>    </DateRange></template>

Inline

Sem o campo de texto nem o popup.

setembro 2026
DSTQQSS
12345
6789101112
13141516171819
20212223242526
27282930
outubro 2026
DSTQQSS
123
45678910
11121314151617
18192021222324
25262728293031
<script setup lang="ts">import { DateRange, type DateRangeValue } from '@vitral/vue';import { ref } from 'vue'; const at = (offset: number) => {    const d = new Date();    d.setDate(d.getDate() + offset);    d.setHours(0, 0, 0, 0);    return d;}; const inline = ref<DateRangeValue>({ start: at(1), end: at(5) });</script> <template>    <DateRange v-model="inline" inline aria-label="Dates" /></template>

Mostrando os dias vizinhos mesmo assim

`show-other-months` passa por cima do que o número de calendários decidiu. Dois meses com as bordas desenhadas: os últimos dias de fevereiro aparecem de novo como os primeiros de março, e um intervalo que cruza a virada é pintado duas vezes.

setembro 2026
DSTQQSS
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
outubro 2026
DSTQQSS
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567
<script setup lang="ts">import { DateRange, type DateRangeValue } from '@vitral/vue';import { ref } from 'vue'; const at = (offset: number) => {    const d = new Date();    d.setDate(d.getDate() + offset);    d.setHours(0, 0, 0, 0);    return d;}; const inline = ref<DateRangeValue>({ start: at(1), end: at(5) });</script> <template>    <DateRange v-model="inline" inline show-other-months aria-label="Dates, edges drawn" /></template>

Como usar

<script setup lang="ts">import { DateRange, type DateRangeValue } from '@vitral/vue';import { ref } from 'vue'; const stay = ref<DateRangeValue>({ start: null, end: null });</script> <template>    <DateRange v-model="stay" :min-date="new Date()" show-clear-button aria-label="Stay" /></template>

API

Lido de packages/vue/src/components/DateRange/types.ts, então diz o que o componente aceita de fato.

Props

NomeTipoDescrição
monthsnumberQuantos meses mostrar lado a lado. Dois é o padrão e a razão de ser deste componente: um intervalo que cruza a virada do mês é escolhido num só gesto em vez de paginando. Um mostra o mesmo intervalo num único calendário.
showOtherMonthsbooleanSe os dias de antes e depois de cada mês são desenhados. Sem definir, ficam ligados num único calendário e desligados em vários: com dois meses lado a lado os últimos dias de um são os primeiros do outro, então a mesma data aparece duas vezes, um intervalo é pintado nas duas cópias e a fronteira entre os calendários deixa de significar algo. As células permanecem — a grade mantém suas seis linhas e sua altura — apenas ficam vazias.
dateFormatstringUm padrão de `formatDate`; o padrão é o `dateFormat` do locale.
separatorstringEntre as duas datas na caixa de texto.
minDateDate | null—
maxDateDate | null—
disabledDatesDate[]—
disabledDaysnumber[]Dias da semana que não podem ser escolhidos, 0 = domingo.
showClearButtonbooleanUm botão no rodapé que esvazia o intervalo.
firstDayOfWeeknumber0 = domingo; o padrão é o `firstDayOfWeek` do locale.
placeholderstring—
inlinebooleanMostra os calendários no lugar, sem caixa de texto nem popup.
sizeSize—
variantInputVariantO padrão é o `inputVariant` do plugin.
invalidboolean—
disabledboolean—
readonlyboolean—
fluidboolean—
placementOverlayPlacement—
appendTostring`'body'` (o padrão), `'self'` para renderizar no lugar, ou um seletor.

Mais pt, dt e unstyled de BaseProps; veja pass-through e modo sem estilo.

Emits

EventoPayloadDescrição
rangeSelectrange: DateRangeValueAs duas pontas foram marcadas. Dispara quando o intervalo está completo, não no primeiro clique.
clear——
show——
hide——
monthChangeevent: { month: number; year: number }—

Slots

NomeProps do slotDescrição
date(props: { date: Date; day: number; today: boolean; inRange: boolean; end: 'start' | 'end' | null; disabled: boolean; otherMonth: boolean })O conteúdo de um dia.
footer(props: { range: DateRangeValue; nights: number; clear: () => void })Abaixo dos calendários: presets, uma contagem de noites, um par de botões.