A small, framework-agnostic toolkit. Built to stop copy-pasting the
same formatCHF / slugify / clamp functions between Next.js projects.
No runtime dependencies. Tree-shakeable via subpath exports.
npm install @mikespa/kitImport only what you need via subpaths — this keeps bundles small and is self-documenting at the call site.
import { formatDate, formatCHF, formatSecs, formatHours } from '@mikespa/kit/format';
import { slugify, truncate } from '@mikespa/kit/string';
import { clamp, timeDiffHours } from '@mikespa/kit/math';
formatDate('2026-06-19'); // "19.06.2026"
formatCHF(1234.5); // "1'234.50 CHF"
formatSecs(9000); // "2h30"
formatHours(2.5); // "2h30"
slugify('Hello, World!'); // "hello-world"
clamp(15, 0, 10); // 10
timeDiffHours('08:00', '10:30'); // 2.5Or import everything from the root if you don't care about tree-shaking:
import { formatCHF, slugify, clamp } from '@mikespa/kit';| Function | Description | Default output |
|---|---|---|
formatDate(input) |
Date as DD.MM.YYYY | "19.06.2026" |
formatTime(input) |
Time (24h) | "15:30" |
formatDateTime(input) |
Date + time (locale-formatted) | "19 juin 2026, 15:30" |
formatDateRange(start, end) |
Compact date range (locale-formatted) | "01.06.2026 – 15.06.2026" |
formatRelative(input) |
Relative to now (locale-formatted) | "il y a 3 heures" |
isToday(input) |
Whether date is today | true |
isPast(input) |
Whether date is in the past | true |
isFuture(input) |
Whether date is in the future | false |
isDateInRange(input, start, end) |
Whether date falls within [start, end] | true |
startOfDay(input) |
Midnight of the given date | Date |
endOfDay(input) |
23:59:59.999 of the given date | Date |
parseSwissDate(input) |
Parse "DD.MM.YYYY" → ISO string | "2026-06-19" |
formatNumber(value) |
Locale-aware thousands | "1 234 567" |
formatCHF(amount) |
Swiss CHF, apostrophe sep. | "1'234.50 CHF" |
formatCurrency(value) |
Intl currency (CHF default) | "1 234.50 CHF" |
formatCompact(value) |
Compact notation | "1,2 k" |
parseCHF(input) |
Parse Swiss amount → number | 1234.5 |
formatPercent(value) |
Percentage | "45,6%" |
formatBytes(bytes) |
Human-readable file size | "1.5 KB" |
formatPhone(input) |
Swiss phone number | "+41 79 123 45 67" |
isValidPhone(input) |
Validate Swiss phone number shape | true |
maskPhone(input) |
Mask phone number for safe display | "+41 79 *** ** 67" |
formatIBAN(input) |
IBAN with spaces | "CH56 0483 5012 3456 7800 9" |
isValidIBAN(input) |
Validate IBAN check digits (any country) | true |
maskIBAN(input) |
Mask IBAN for safe display | "CH56 **** **** **** **** 9" |
formatAHV(input) |
AHV/OASI number, grouped | "756.9217.0769.85" |
isValidAHV(input) |
Validate AHV/OASI check digit | true |
maskAHV(input) |
Mask AHV number for safe display | "756...85" |
formatQRReference(input) |
QR-bill/ISR reference, grouped | "21 00000 00003 13947 14300 09017" |
isValidQRReference(input) |
Validate QR-bill/ISR check digit | true |
formatUID(input) |
UID/VAT number, grouped | "CHE-116.281.710" |
isValidUID(input) |
Validate UID/VAT check digit | true |
isValidSwissPostalCode(input) |
Validate 4-digit postal code range | true |
formatSecs(seconds, step?) |
Duration from seconds | "2h30" / "45min" |
formatMinutes(minutes, step?) |
Duration from minutes | "2h30" / "45min" |
formatHours(hours, step?) |
Duration from decimal hours | "2h30" / "45min" |
formatHoursDecimal(hours) |
Decimal duration from hours | "2.50h" |
formatMinutesDecimal(minutes) |
Decimal duration from minutes | "45.00min" |
formatSecsDecimal(seconds) |
Decimal duration from seconds | "90.00s" |
parseDurationInput(input) |
Parse "2h30" / "2.5" → minutes | 150 |
All format* functions that wrap Intl accept an optional locale parameter
(defaults to 'fr-CH') so consuming projects can override per-call.
For apps with a variable/per-request locale (e.g. a language switcher, or SSR with
per-request locale), createFormatters(locale) returns those same functions
pre-bound to one locale, so call sites don't repeat { locale } everywhere. It's
a plain factory — no shared/global state, safe under concurrent requests:
import { createFormatters } from '@mikespa/kit/format';
const fmt = createFormatters('de-CH');
fmt.formatDate(order.date);
fmt.formatCurrency(order.total);createFormatters binds: formatDate, formatTime, formatDateTime,
formatRelative, formatNumber, formatCompact, formatCurrency, formatPercent.
| Function | Description |
|---|---|
slugify(input) |
URL-friendly slug |
truncate(input, maxLength) |
Truncate with suffix |
capitalize(input) |
First letter uppercase |
pluralize(count, singular) |
Simple pluralizer |
| Function | Description |
|---|---|
clamp(value, min, max) |
Clamp to range |
lerp(start, end, t) |
Linear interpolate |
round(value, decimals) |
Round to N decimals |
timeDiffHours(from, to, overnight?) |
Hours between "HH:MM" strings |
| Function | Description |
|---|---|
groupBy(arr, keyFn) |
Group items into a Record by key |
uniqueBy(arr, keyFn) |
Deduplicate by derived key |
sortBy(arr, keyFn, dir?) |
Sort by derived key, asc or desc |
| Function | Description |
|---|---|
pick(obj, keys) |
New object with only specified keys |
omit(obj, keys) |
New object with specified keys removed |
npm install
npm run dev # build in watch mode
npm run build # build once (output -> dist/)
npm test # run tests
npm run test:watch
npm run typecheckMIT