CountdownNew
Live component. Click, hover, and type to try it.
Install
npx shadcn@latest add https://mdxcn.dev/r/graph-countdown.jsonAbout
Time left until a date. After that it shows a short label you pass in.
Live component. Click, hover, and type to try it.
npx shadcn@latest add https://mdxcn.dev/r/graph-countdown.jsonTime left until a date. After that it shows a short label you pass in.
"use client"
import type { ReactNode } from "react"
import { motion, useReducedMotion } from "motion/react"
import {
Graph,
GraphBody,
splitDash,
textOf,
} from "@/registry/default/graph-frame/graph-frame"
import {
formatHms,
parseInstant,
useGraphNow,
} from "@/registry/default/graph-frame/graph-clock"
import {
fadeUp,
toneClass,
type GraphPalette,
} from "@/registry/default/graph-frame/graph-motion"
import { cn } from "@/lib/utils"
type GraphCountdownProps = {
title: string
/** Or write it as children. */
to?: Date | number | string
done?: string
caption?: string
/** Markdown: the instant, then the caption. `2026-12-01 — until launch`. */
children?: ReactNode
palette?: GraphPalette
corner?: string
className?: string
}
function GraphCountdown({
title,
to: toProp,
done = "done",
caption: captionProp,
children,
palette,
corner,
className,
}: GraphCountdownProps) {
const reduce = useReducedMotion()
const enter = fadeUp(reduce)
const now = useGraphNow()
const written = splitDash(textOf(children).replace(/\s+/g, " ").trim())
const to = toProp ?? written.label
const caption = captionProp ?? (written.rest || undefined)
const target = to ? parseInstant(to) : Number.NaN
const remaining =
now == null || !Number.isFinite(target) ? null : target - now
const finished = remaining != null && remaining <= 0
const value =
remaining == null ? "00:00:00" : finished ? done : formatHms(remaining)
return (
<Graph title={title} className={className} corner={corner}>
<GraphBody>
<motion.div
className="flex flex-col gap-2"
initial={reduce ? false : "hidden"}
variants={enter}
viewport={{ once: true, amount: 0.5 }}
whileInView="show"
>
<p
className={cn(
"text-3xl tracking-tight tabular-nums sm:text-4xl",
finished ? "text-graph-muted" : toneClass(palette, "primary")
)}
>
{value}
</p>
{caption ? <p className="text-graph-muted">{caption}</p> : null}
</motion.div>
<span className="sr-only">
{finished ? done : `remaining ${value}`}
</span>
</GraphBody>
</Graph>
)
}
export { GraphCountdown }
export type { GraphCountdownProps }