Countdown
Precise countdown timer calculated from absolute timestamp differences. Self-calibrates when recovering from background tab throttling to eliminate cumulative timer drift, paired with accessible screen reader announcements. To keep SSR hydration consistent, both the server and the client's initial render show placeholder text; the browser clock is read and timing starts after mount.
Preview
Installation
pnpm dlx brutx-vue@latest add countdownUsage
<script setup>
import { onMounted, ref } from 'vue'
import { Countdown } from 'brutx-ui-vue'
const ONE_DAY_MS = 24 * 60 * 60 * 1000
const targetTime = ref<number | null>(null)
onMounted(() => {
targetTime.value = Date.now() + ONE_DAY_MS
})
</script>
<template>
<!-- Basic countdown -->
<Countdown title="Time Left" :value="targetTime" />
<!-- Custom format and card layout -->
<Countdown
title="Deadline"
:value="targetTime"
format="DD [days] HH [hours] mm [mins] ss [secs]"
variant="card"
/>
</template>SSR First Frame and Custom Content
During SSR and hydration, the component displays placeholder (default -) first, then calculates the remaining time from value after mount. The default slot's formatted value is also placeholder text until then; remaining is 0 and isFinished is false. isReady becomes true after mount and the first calculation; remaining and isFinished then reflect the current target.
<Countdown :value="targetTime">
<template #default="{ formatted, isReady }">
{{ isReady ? formatted : 'Preparing countdown' }}
</template>
</Countdown>Millisecond Refresh and Escaping
Including SSS in the format string automatically enables high-frequency (~30fps) refreshes. Literal letters matching format tokens (like D, H, m, s) can be escaped inside brackets [...]:
<script setup>
import { onMounted, ref } from 'vue'
const ONE_MINUTE_MS = 60 * 1000
const targetTime = ref<number | null>(null)
onMounted(() => {
targetTime.value = Date.now() + ONE_MINUTE_MS
})
</script>
<template>
<Countdown
title="Flash Sale"
:value="targetTime"
format="mm:ss.SSS"
variant="card"
/>
</template>Finish and Change Events
Listen to the @finish event when countdown hits zero, and monitor @change for remaining milliseconds:
<script setup>
import { onMounted, ref } from 'vue'
import { Countdown } from 'brutx-ui-vue'
const FIVE_SECONDS_MS = 5 * 1000
const targetTime = ref<number | null>(null)
onMounted(() => {
targetTime.value = Date.now() + FIVE_SECONDS_MS
})
function onFinish() {
console.log('Countdown reached zero!')
}
function onChange(remainingMs: number) {
console.log('Remaining milliseconds:', remainingMs)
}
</script>
<template>
<Countdown
:value="targetTime"
@finish="onFinish"
@change="onChange"
/>
</template>Variants
| Variant | Description |
|---|---|
default | Default variant with lightweight inline layout and no extra border/background |
card | Neo-Brutalist card with 3px solid border, hard drop shadow, padding, and rounded corners |
bordered | Solid bordered card container without drop shadow |
subtle | Highlighted subtle background card with solid border and hard drop shadow |
<template>
<Countdown title="Default" :value="targetTime" variant="default" />
<Countdown title="Card" :value="targetTime" variant="card" />
<Countdown title="Bordered" :value="targetTime" variant="bordered" />
<Countdown title="Subtle" :value="targetTime" variant="subtle" />
</template>Sizes
| Size | Title Font Size | Value Font Size |
|---|---|---|
sm | text-xs | text-2xl |
default | text-sm | text-3xl sm:text-4xl |
lg | text-base | text-4xl sm:text-5xl |
<template>
<Countdown title="Small" :value="targetTime" size="sm" variant="card" />
<Countdown title="Default" :value="targetTime" size="default" variant="card" />
<Countdown title="Large" :value="targetTime" size="lg" variant="card" />
</template>Exported Types
The component exports variant helper functions and format parsing utilities:
import type {
CountdownProps,
CountdownValue,
CountdownEmits,
CountdownVariantProps,
} from 'brutx-ui-vue'
import {
countdownVariants,
parseCountdownTarget,
formatCountdown,
} from 'brutx-ui-vue'API Reference
| Name | Type | Default | Description |
|---|---|---|---|
valueNullable | CountdownValue | undefined | The target deadline, as a millisecond timestamp, Date, numeric timestamp string, or parseable date string. Empty or invalid values show the placeholder. |
DetailsDeclared typeCountdownValue Literal values
Referenced types CountdownValue Type definition — CountdownValueexport type CountdownValue = number | string | Date | null | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
format | string | undefined | "HH:mm:ss" | The remaining-time format, supporting DD, D, HH, H, mm, m, ss, s, SSS, SS, S, and literal text in square brackets. Formats containing S use millisecond-level updates. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
title | string | undefined | undefined | The title above the countdown value; the title slot can replace its content. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
prefix | string | undefined | undefined | Text before the countdown value; the prefix slot can replace its content. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
suffix | string | undefined | undefined | Text after the countdown value; the suffix slot can replace its content. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
placeholder | string | undefined | undefinedRuntime fallback: | The placeholder displayed before mount or when the target time is empty or invalid. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
variant | "bordered" | "card" | "default" | "subtle" | undefined | "default" | The background and border variant of the countdown container. |
DetailsDeclared typeNonNullable<"default" | "card" | "bordered" | "subtle" | null | undefined> | undefined Literal values
Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
size | "default" | "lg" | "sm" | undefined | "default" | The size passed to the inner Statistic, controlling the value and accompanying text. |
DetailsDeclared typeNonNullable<"default" | "lg" | "sm" | null | undefined> | undefined Literal values
Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
class | string | undefined | undefined | Custom CSS classes merged onto the countdown container. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/countdown/types.ts | |||
| Name | Signature or type | Description |
|---|---|---|
@change | [remaining: number] | Emits the remaining milliseconds, clamped at zero, when the timer updates or the target is recalculated. |
DetailsDeclared type[remaining: number] Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
@finish | [] | Emitted when a valid countdown enters its finished state, including an already-expired target at mount. A new future target restarts the countdown and allows it to finish again. |
DetailsDeclared type[] Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
| Name | Signature or type | Description |
|---|---|---|
#title | { title?: string | undefined; } | Customizes the title area with the title prop value, which is rendered by default. |
DetailsDeclared type{ title?: string | undefined; }Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
#prefix | { prefix?: string | undefined; } | Customizes the value prefix area with the prefix prop value. |
DetailsDeclared type{ prefix?: string | undefined; }Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
#suffix | { suffix?: string | undefined; } | Customizes the value suffix area with the suffix prop value. |
DetailsDeclared type{ suffix?: string | undefined; }Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
#default | { remaining: number; formatted: string; isFinished: boolean; isReady: boolean; } | Customizes the value area with the remaining milliseconds, formatted string, finished state, and mounted isReady state. |
DetailsDeclared type{ remaining: number; formatted: string; isFinished: boolean; isReady: boolean; }Sourcepackages/ui/src/components/countdown/Countdown.vue | ||
Accessibility
- Keyboard Interaction: Pure temporal display component that does not trap keyboard input; embedded interactive controls follow standard focus flow.
- ARIA Attributes: Keeps
aria-livequiet during high-frequency ticks to avoid screen reader spam; triggers a singlearia-live="polite"speech announcement when countdown reaches zero. - Focus Management: Non-modal inline layout without focus traps, respecting assistive cursor navigation.
- Number Layout: Uses monospace numerals (
font-mono) to reduce width changes during updates. Remaining time is calculated from the current clock and timer scheduling begins after mount. Custom default slots can useisReadyto distinguish the placeholder first frame from calculated values.