
Research
/Security News
77 Firefox Extensions Linked to Crypto Wallet and Credential Theft
Socket uncovered 77 linked Firefox extensions, including 40 that steal wallet secrets or credentials and 37 deceptive sports-score shells.
Timer, countdown, and stopwatch hooks and components for Ink.
npm install ink-timer
Peer dependencies: react (>=18) and ink (>=5).
import { render } from 'ink';
import { Timer } from 'ink-timer';
render(<Timer prefix="Elapsed: " format="human" enableKeyboard />);
// Elapsed: 0s
// Elapsed: 1s
// Elapsed: 2s ...
import { render } from 'ink';
import { Countdown } from 'ink-timer';
render(
<Countdown
duration={30_000}
prefix="Remaining: "
color="yellow"
onComplete={() => console.log('Done!')}
/>
);
// Remaining: 00:30
// Remaining: 00:29 ...
import { render } from 'ink';
import { Stopwatch } from 'ink-timer';
render(
<Stopwatch enableKeyboard showLaps maxLapsDisplay={5} color="cyan" />
);
// 00:00
// (press L to record laps)
useTimer(options?)A hook that counts elapsed time upward.
| Option | Type | Default | Description |
|---|---|---|---|
autoStart | boolean | true | Start the timer immediately on mount |
interval | number | 1000 | Update interval in milliseconds |
onTick | (elapsedMs: number) => void | - | Callback fired on every interval tick while running |
format | FormatOption | "digital" | Format preset name or custom format function |
UseTimerResult| Property | Type | Description |
|---|---|---|
elapsedMs | number | Elapsed time in milliseconds since start (excluding paused periods) |
isRunning | boolean | Whether the timer is currently running |
formatted | FormattedTime | Formatted time breakdown (see below) |
start | () => void | Start or resume the timer. No-op if already running |
stop | () => void | Pause the timer. No-op if not running |
reset | () => void | Reset to 0 and stop |
toggle | () => void | Toggle between running and stopped |
useCountdown(options)A hook that counts down from a given duration.
| Option | Type | Default | Description |
|---|---|---|---|
duration | number | required | Total countdown duration in milliseconds |
autoStart | boolean | true | Start counting down immediately on mount |
interval | number | 1000 | Update interval in milliseconds |
onTick | (remainingMs: number) => void | - | Callback fired on every interval tick while running |
onComplete | () => void | - | Callback fired exactly once when the countdown reaches 0 |
format | FormatOption | "digital" | Format preset name or custom format function |
UseCountdownResult| Property | Type | Description |
|---|---|---|
remainingMs | number | Remaining time in milliseconds (clamped to >= 0) |
isRunning | boolean | Whether the countdown is currently running |
isComplete | boolean | Whether the countdown has reached 0 |
formatted | FormattedTime | Formatted time breakdown of remaining time |
start | () => void | Start or resume. No-op if already running or complete |
stop | () => void | Pause. No-op if not running |
reset | () => void | Reset to the original duration and stop |
toggle | () => void | Toggle between running and stopped |
restart | () => void | Reset and immediately start |
useStopwatch(options?)A hook that counts elapsed time upward with lap recording.
| Option | Type | Default | Description |
|---|---|---|---|
autoStart | boolean | true | Start the stopwatch immediately on mount |
interval | number | 1000 | Update interval in milliseconds |
onTick | (elapsedMs: number) => void | - | Callback fired on every interval tick while running |
onLap | (lap: Lap) => void | - | Callback fired when a new lap is recorded |
format | FormatOption | "digital" | Format preset name or custom format function |
UseStopwatchResult| Property | Type | Description |
|---|---|---|
elapsedMs | number | Total elapsed time in milliseconds (excluding paused periods) |
isRunning | boolean | Whether the stopwatch is currently running |
laps | readonly Lap[] | Array of recorded laps in chronological order |
formatted | FormattedTime | Formatted time breakdown |
start | () => void | Start or resume. No-op if already running |
stop | () => void | Pause. No-op if not running |
reset | () => void | Reset to 0, clear all laps, and stop |
toggle | () => void | Toggle between running and stopped |
lap | () => void | Record a lap. Only works while running |
Lap| Property | Type | Description |
|---|---|---|
number | number | 1-indexed lap number |
durationMs | number | Duration of this individual lap in milliseconds |
cumulativeMs | number | Cumulative elapsed time at the moment this lap was recorded |
formatted | FormattedTime | Formatted breakdown of this lap's duration |
Note: Each lap's
formattedfield is a snapshot created at recording time using the format option that was active when the lap was recorded. If you change the format option after recording laps, previously recorded laps retain their original formatting.
FormattedTimeReturned by the formatted field of every hook.
| Property | Type | Description |
|---|---|---|
text | string | Pre-formatted display string based on the active format option |
hours | number | Whole hours component |
minutes | number | Whole minutes component (0-59) |
seconds | number | Whole seconds component (0-59) |
milliseconds | number | Remaining milliseconds component (0-999) |
totalMs | number | Total elapsed or remaining time in milliseconds |
<Timer>Renders a timer display that counts up.
| Prop | Type | Default | Description |
|---|---|---|---|
autoStart | boolean | true | Start automatically on mount |
interval | number | 1000 | Update interval in milliseconds |
format | FormatOption | "digital" | Format preset or custom format function |
showMilliseconds | boolean | false | Shorthand to switch to "digital-ms" format preset |
prefix | string | - | Text to render before the time string |
suffix | string | - | Text to render after the time string |
color | string | - | Text color (any value supported by Ink's <Text color>) |
bold | boolean | false | Render time text in bold |
dimWhenPaused | boolean | true | Dim the time text when paused |
enableKeyboard | boolean | false | Enable keyboard controls |
onTick | (elapsedMs: number) => void | - | Callback fired every tick |
<Countdown>Renders a countdown display.
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | required | Total countdown duration in milliseconds |
autoStart | boolean | true | Start automatically on mount |
interval | number | 1000 | Update interval in milliseconds |
format | FormatOption | "digital" | Format preset or custom format function |
showMilliseconds | boolean | false | Shorthand to switch to "digital-ms" format preset |
prefix | string | - | Text to render before the time string |
suffix | string | - | Text to render after the time string |
color | string | - | Text color |
bold | boolean | false | Render time text in bold |
dimWhenPaused | boolean | true | Dim the time text when paused |
enableKeyboard | boolean | false | Enable keyboard controls |
onTick | (remainingMs: number) => void | - | Callback fired every tick |
onComplete | () => void | - | Callback fired when countdown reaches 0 |
<Stopwatch>Renders a stopwatch display with optional lap list.
| Prop | Type | Default | Description |
|---|---|---|---|
autoStart | boolean | true | Start automatically on mount |
interval | number | 1000 | Update interval in milliseconds |
format | FormatOption | "digital" | Format preset or custom format function |
showMilliseconds | boolean | false | Shorthand to switch to "digital-ms" format preset |
prefix | string | - | Text to render before the time string |
suffix | string | - | Text to render after the time string |
color | string | - | Text color |
bold | boolean | false | Render time text in bold |
dimWhenPaused | boolean | true | Dim the time text when paused |
enableKeyboard | boolean | false | Enable keyboard controls |
onTick | (elapsedMs: number) => void | - | Callback fired every tick |
onLap | (lap: Lap) => void | - | Callback fired when a lap is recorded |
showLaps | boolean | true | Show lap list below the elapsed time |
maxLapsDisplay | number | 0 | Maximum laps to display (most recent first). 0 = unlimited |
enableLapKey | boolean | true | Enable the L key for recording laps (only when enableKeyboard is true) |
"digital" (default)Minutes and seconds, with hours added automatically when needed.
0s -> 00:00
5s -> 00:05
2m 30s -> 02:30
1h 1m -> 1:01:01
"digital-ms"Same as "digital" but with milliseconds appended.
0s -> 00:00.000
2m 30.45s -> 02:30.450
1h 1m 1.12s -> 1:01:01.123
"human"Human-readable short units.
0s -> 0s
5s -> 5s
2m 30s -> 2m 30s
1h 1m -> 1h 1m 1s
"human-ms"Same as "human" but with milliseconds appended.
0s -> 0s 0ms
2m 30.45s -> 2m 30s 450ms
1h 1m 1.12s -> 1h 1m 1s 123ms
Pass a function that receives milliseconds and returns a string.
<Timer
format={(ms) => `${Math.floor(ms / 1000)} seconds`}
/>
// 0 seconds
// 1 seconds
// 2 seconds ...
The formatted object still provides numeric hours, minutes, seconds, and milliseconds components regardless of the format function.
When enableKeyboard is set to true, the following keys are active:
| Key | Action | Components |
|---|---|---|
Space | Pause / resume | Timer, Countdown, Stopwatch |
R / r | Reset | Timer, Countdown, Stopwatch |
L / l | Record a lap | Stopwatch (when enableLapKey is true) |
import { useTimer } from 'ink-timer';
import { Text, Box } from 'ink';
function PauseableTimer() {
const { formatted, isRunning, start, stop } = useTimer({ autoStart: false });
return (
<Box flexDirection="column">
<Text>{formatted.text}</Text>
<Text dimColor>
{isRunning ? 'Running (call stop() to pause)' : 'Paused (call start() to resume)'}
</Text>
</Box>
);
}
import { useCountdown } from 'ink-timer';
import { Text } from 'ink';
function FriendlyCountdown() {
const { formatted, isComplete } = useCountdown({
duration: 60_000,
format: (ms) => {
const s = Math.ceil(ms / 1000);
if (s > 10) return `${s} seconds left`;
if (s > 0) return `${s}...`;
return 'Go!';
},
});
return <Text color={isComplete ? 'green' : 'yellow'}>{formatted.text}</Text>;
}
import { Timer, Countdown } from 'ink-timer';
import { Box, Text } from 'ink';
function Dashboard() {
return (
<Box flexDirection="column" gap={1}>
<Box>
<Text>Uptime: </Text>
<Timer format="human" color="green" />
</Box>
<Box>
<Text>Break ends in: </Text>
<Countdown
duration={5 * 60_000}
format="digital"
color="yellow"
onComplete={() => console.log('Break over!')}
/>
</Box>
</Box>
);
}
import { useStopwatch } from 'ink-timer';
import { Text, Box } from 'ink';
function LapTracker() {
const { formatted, laps, lap, reset } = useStopwatch({
format: 'digital-ms',
interval: 100,
});
return (
<Box flexDirection="column">
<Text bold>{formatted.text}</Text>
{laps.map((l) => (
<Text key={l.number}>
Lap {l.number}: {l.formatted.text} (total: {l.cumulativeMs}ms)
</Text>
))}
</Box>
);
}
All types are exported from the package entry point:
import type {
FormatFunction,
FormatPreset,
FormatOption,
FormattedTime,
UseTimerOptions,
UseTimerResult,
UseCountdownOptions,
UseCountdownResult,
UseStopwatchOptions,
UseStopwatchResult,
Lap,
TimerProps,
CountdownProps,
StopwatchProps,
TimerDisplayProps,
} from 'ink-timer';
Contributions welcome! Please open an issue first to discuss what you'd like to change.
See GitHub releases.
MIT
FAQs
Timer, countdown, and stopwatch hooks and components for Ink
The npm package ink-timer receives a total of 15 weekly downloads. As such, ink-timer popularity was classified as not popular.
We found that ink-timer demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Socket uncovered 77 linked Firefox extensions, including 40 that steal wallet secrets or credentials and 37 deceptive sports-score shells.

Security News
NIST disclosed an unreleased AI tool called V-etalon and opened a broad inquiry into NVD modernization after years of automation plans produced no public enrichment system.

Security News
In his AI Council 2026 talk, Feross Aboukhadijeh covers recent package compromises, vulnerability discovery, and a more automated security model.