Use it for
Use BbTimePicker to set a recurring schedule time from your own cell, chip, or
button.
Use something else when
BbTimePickerInput: people must type the time or submit it as a labelled form fieldBbDatePicker: the time belongs to a specific calendar date
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
BbTimePicker is new in v3.
Set a schedule time
Use one picker per schedule value, such as Monday's opening time.
null<template>
<div class="flex items-center gap-3">
<BbTimePicker v-model="opensAt" label="Opening time on Monday">
<!-- `v-bind="props"` is the whole wiring: reference, click and ARIA. -->
<template #activator="{ props, value }">
<button v-bind="props" class="cell" type="button">
{{ value ?? 'Set opening time' }}
</button>
</template>
</BbTimePicker>
<span class="text-sm opacity-70">
Model — <code>{{ opensAt ?? 'null' }}</code>
</span>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbTimePicker } from 'bitboss-ui';
// `null` is the "no time yet" value. A 24-hour `HH:mm` string otherwise.
const opensAt = ref<string | null>(null);
</script>
<style scoped>
.cell {
border: var(--bb-border-w) solid var(--bb-border);
border-radius: var(--bb-radius);
font-size: 0.875rem;
outline: none;
padding: 0.25rem 0.625rem;
}
.cell:focus-visible {
box-shadow:
0 0 0 2px var(--bb-panel),
0 0 0 4px var(--bb-ring);
}
</style>
The model is null or a zero-padded 24-hour string such as 09:00. It is a
wall-clock value: it has no date or timezone and does not shift between regions.
Spread the activator slot's props onto the clickable element. Give the picker a
specific label; the slot wiring supplies the click handler, accessible state,
and focus return.
Display and granularity
Use ampm for a 12-hour display, seconds for HH:mm:ss, and step for the
minute rows offered by the columns.
<template>
<div class="flex flex-wrap items-center gap-2">
<BbTimePicker v-model="pickup" ampm label="Pickup time" :step="15">
<template #activator="{ props, value }">
<button v-bind="props" class="chip" type="button">
ampm + step 15: {{ value ?? '—' }}
</button>
</template>
</BbTimePicker>
<BbTimePicker v-model="measured" label="Measurement time" seconds>
<template #activator="{ props, value }">
<button v-bind="props" class="chip" type="button">
seconds: {{ value ?? '—' }}
</button>
</template>
</BbTimePicker>
<BbTimePicker
v-model="callback"
label="Callback time"
:max="fixedTimes.closesAt"
:min="fixedTimes.opensAt"
>
<template #activator="{ props, value }">
<button v-bind="props" class="chip" type="button">
09:00–18:00 only: {{ value ?? '—' }}
</button>
</template>
</BbTimePicker>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbTimePicker } from 'bitboss-ui';
import { fixedTimes } from '~/demo-data';
// `ampm` labels the columns 12 AM … 11 PM; the emitted string stays 24-hour.
const pickup = ref<string | null>(fixedTimes.slot);
const measured = ref<string | null>(fixedTimes.precise);
const callback = ref<string | null>(null);
</script>
<style scoped>
.chip {
border: var(--bb-border-w) solid var(--bb-border);
border-radius: var(--bb-radius);
font-size: 0.875rem;
outline: none;
padding: 0.25rem 0.625rem;
}
.chip:focus-visible {
box-shadow:
0 0 0 2px var(--bb-panel),
0 0 0 4px var(--bb-ring);
}
</style>
These props do not change the value contract. ampm still emits 24-hour text,
and step constrains picks without rounding an existing off-grid value. min
and max accept HH:mm.
Schedule windows
Use range for a shift or service window, with [] as the empty model.
start<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<BbTimePicker
v-model="shift"
v-model:active-segment="editing"
label="Shift hours"
range
:step="30"
>
<template #activator="{ props, value }">
<button v-bind="props" class="chip" type="button">
{{
Array.isArray(value) && value.length === 2
? `${value[0]} → ${value[1]}`
: 'Pick a shift'
}}
</button>
</template>
</BbTimePicker>
<span class="text-sm opacity-70">
editing <code>{{ editing }}</code>
</span>
</div>
<div class="flex gap-2">
<BbButton size="xs" variant="outline" @click="editing = 'start'">
Edit the start
</BbButton>
<BbButton size="xs" variant="outline" @click="editing = 'end'">
Edit the end
</BbButton>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbButton, BbTimePicker } from 'bitboss-ui';
import type { BbTimePickerSegment } from 'bitboss-ui';
import { fixedTimes } from '~/demo-data';
// An array at every moment, including the empty one: `[]`, never `null`.
const shift = ref<string[]>([fixedTimes.shiftStart, fixedTimes.shiftEnd]);
// The toggle above the columns is the picker's own affordance; this model
// lets you read it, and drive it from outside.
const editing = ref<BbTimePickerSegment>('start');
</script>
<style scoped>
.chip {
border: var(--bb-border-w) solid var(--bb-border);
border-radius: var(--bb-radius);
font-size: 0.875rem;
outline: none;
padding: 0.25rem 0.625rem;
}
.chip:focus-visible {
box-shadow:
0 0 0 2px var(--bb-panel),
0 0 0 4px var(--bb-ring);
}
</style>
The panel toggle chooses which end is being edited. Bind
v-model:active-segment only when another control must read or change that end.
An inverted pair is swapped on commit and emits end_before_start.
Trigger and mobile surface
Use the activator prop for an existing element, or v-bb-time for a bare
schedule cell that does not need slot state. The columns support arrow keys and
digit type-ahead; do not add parallel key handlers.
On mobile, adaptive opens the columns in a bottom sheet. Pass options through
offCanvasProps; set :adaptive="false" for viewport-independent tests.
Desktop placement props do not apply to the sheet.