sleepy-countdown
v1.0.0
Published
A configurable React countdown timer component with modern, classic, and minimal designs.
Maintainers
Readme
⏳ sleepy-countdown
A drop-in React countdown timer with three pre-designed looks and a CSS-variable theming API.
sleepy-countdown is a single React component that counts down to a date and renders
itself as a self-contained card. Pick one of three designs with the type prop
(modern, classic, minimal), choose how fine-grained the clock is with
resolution, and recolor everything through --sleepy-countdown-* CSS custom
properties. Zero runtime dependencies, styles bundled into the JS, full TypeScript
types, and the card never changes size — not when units drop off, not when the
countdown ends.
Install
npm install sleepy-countdown # or: pnpm add / yarn add / bun addreact and react-dom (v18 or newer) are peer dependencies — your app provides
them. Nothing else is installed: the package ships no runtime dependencies of its own.
Quick start
import { SleepyCountdown } from 'sleepy-countdown';
export function Example() {
return (
<SleepyCountdown
target={new Date('2027-01-01T00:00:00')}
type="modern"
resolution="second"
title="New Year"
description="Counting down to 2027"
end_message="Happy New Year!"
/>
);
}That's the whole setup. No CSS import is required — the styles are injected by the bundle, so the component looks right the moment it renders.
The three types
Each type renders the same computed numbers with a different treatment. All three sit
inside an identical frame (title → description → fixed-height timer), so you can mix
them in one grid without the layout going ragged.
| Type | Look |
| --- | --- |
| modern (default) | Rounded cards, accent-colored digits, small caption labels |
| classic | Flip-clock cards split across the middle — each digit change replays a flip-in animation |
| minimal | Bare colon-separated numbers with tiny labels, no card chrome |
Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| target | Date \| string \| number | (required) | When the countdown ends — a Date, an ISO string, or epoch milliseconds. |
| type | 'modern' \| 'classic' \| 'minimal' | 'modern' | Pre-designed visual style. An unknown value falls back to modern. |
| resolution | 'day' \| 'hour' \| 'minute' \| 'second' | 'day' | Finest unit shown; finer units are hidden. |
| title | ReactNode | — | Heading shown above the timer. |
| description | ReactNode | — | Muted text shown below the title. |
| end_message | ReactNode | 'Countdown has ended.' | Replaces the timer once target passes. |
| className | string | — | Extra class on the root element. |
| style | CSSProperties | — | Inline styles / CSS-variable overrides on the root element. |
title, description, and end_message are ReactNodes, so they take markup — not
just strings.
How resolution works
Units are always coarse → fine, and resolution sets where the list stops. Nothing is
rolled up: at resolution="hour" the days card still shows days, and the leftover
minutes and seconds are simply not displayed.
| resolution | Units rendered |
| --- | --- |
| day | Days |
| hour | Days · Hours |
| minute | Days · Hours · Minutes |
| second | Days · Hours · Minutes · Seconds |
Values are zero-padded to two digits, and the component re-renders once per second regardless of resolution.
Theming
Every cosmetic value is a CSS custom property with a sensible default, so you can
recolor the component from your own stylesheet or the style prop. The default palette
is Catppuccin Mocha; override any variable to switch
flavors — or to your own brand.
Fonts are deliberately not set — text inherits your app's typography. See
docs/style_inheritence.md for the full contract.
| Variable | Default (Catppuccin Mocha) | Controls |
| --- | --- | --- |
| --sleepy-countdown-fg | #cdd6f4 (Text) | Primary text color |
| --sleepy-countdown-muted | #a6adc8 (Subtext0) | Muted text (description, unit labels) |
| --sleepy-countdown-accent | #cba6f7 (Mauve) | Accent color (modern digits) |
| --sleepy-countdown-surface | #1e1e2e (Base) | The card's background |
| --sleepy-countdown-border | 1px solid #313244 | The card's border (full shorthand) |
| --sleepy-countdown-surface-radius | 18px | The card's corner rounding |
| --sleepy-countdown-padding | 22px 26px | The card's inner padding |
| --sleepy-countdown-shadow | soft dark drop shadow | The card's shadow |
| --sleepy-countdown-card-bg | #313244 (Surface0) | The individual time-unit cards |
| --sleepy-countdown-card-fg | #cdd6f4 (Text) | Text on the time-unit cards (classic) |
| --sleepy-countdown-radius | 12px | Unit-card corner rounding |
| --sleepy-countdown-gap | 10px | Space between unit cards |
| --sleepy-countdown-unit-width | 68px | Width of one unit card |
| --sleepy-countdown-timer-height | 96px | Height of the timer area |
/* Example: override to Catppuccin Latte (the light flavor). */
.my-page .sleepy-countdown {
--sleepy-countdown-surface: #eff1f5;
--sleepy-countdown-border: 1px solid #ccd0da;
--sleepy-countdown-card-bg: #ccd0da;
--sleepy-countdown-accent: #8839ef;
--sleepy-countdown-fg: #4c4f69;
--sleepy-countdown-muted: #6c6f85;
}Target
.sleepy-countdown, not:root. The component sets its defaults directly on.sleepy-countdown, and a custom property set on an element always beats one inherited from an ancestor — so:rootoverrides silently do nothing.
Class names are stable and sleepy--prefixed (.sleepy-countdown,
.sleepy-countdown--modern, .sleepy-modern__value, …) precisely so this kind of
override keeps working. They're part of the public API.
Behavior notes
- The card never resizes. The timer area is a fixed-height box, so dropping units
via
resolution, switchingtype, or ending the countdown all leave the footprint untouched. Grids of timers stay aligned. - Ends gracefully. Once
targetpasses,end_messagetakes the timer's place in the same box; the ticking stops mattering (the remaining time clamps at zero). targetis reactive. Change it and the countdown re-syncs immediately rather than waiting for the next tick.- One interval per component, cleared on unmount.
TypeScript
Types ship with the package — no @types/ install. The public surface:
import type {
SleepyCountdownProps,
Resolution, // 'day' | 'hour' | 'minute' | 'second'
CountdownType, // 'modern' | 'classic' | 'minimal'
} from 'sleepy-countdown';Both ESM (import) and CommonJS (require) builds are published.
Seen in the wild
countdown-timers — a self-hostable web app that renders a grid of these timers from a single YAML file. Available as a Docker image.
Development
npm install
npm run demo # live demo app from examples/, via Vite
npm test # vitest
npm run build # tsup → dist/ (ESM + CJS + .d.ts)Documentation
- Specification — what the component does
- Project outline — repository layout, and how npm packages work
- Style inheritance — the theming contract in full
- Test drive — running the demo and the tests
- Publishing — release to npm
