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);// 10timeDiffHours('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';constfmt=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