Utilities
Fifty-eight helpers across nine groups. Enough that an admin screen never has to pull in lodash, and small enough to read.
Two rules run through all of them. Nothing mutates — sorting a table must not reorder the store behind it. And the awkward case is handled, because the reason a helper exists is usually the edge that a < b or .slice() gets wrong.
The groups
| Group | Functions | What it covers |
|---|---|---|
| Array | 8 | Grouping, indexing, deduplicating, locale-aware sorting, chunking, totals, reordering. |
| String | 7 | Case, slugs, truncation, initials, masking — all grapheme-aware. |
| Number | 7 | Clamping, correct rounding, byte sizes, percentages, numeric input guards. |
| Object | 7 | Pick, omit, safe nested reads, cleaning a query object, a real emptiness test. |
| File | 4 | Downloading a blob, reading a file, the extension. |
| Color | 6 | Hex and RGB, contrast ratio, a readable foreground, a stable color from a string. |
| Intl | 4 | The locale tag, graphemes, and the shared collator. |
| Format | 7 | Dates, numbers, currency and compact figures, on the active locale. |
| Time columns | 8 | The hour and minute columns behind the time picker. |
Import paths
Each group is its own module under @shared/utils:
import { chunk, groupBy, sortBy } from '@shared/utils/array'
import { formatCurrency, formatDate } from '@shared/utils/format'
import { contrastColor } from '@shared/utils/color'Five more live in four modules of their own and are documented with the group they belong to: deepEqual, deepMerge, stripHtml, and the pair in numericInput.
Locale-aware by default
Anything that compares or renders text takes the app's active locale unless you pass one:
sortBygoes throughIntl.Collator, nevera < b, which would putZurichbeforeÁvila.capitalizeandinitialsrespect Turkish dotted I, soismailbecomesİsmailintrandIsmailinen.truncate,initialsandmaskStringcount graphemes, not UTF-16 units, so a ZWJ family emoji is one character rather than eleven.formatBytesand everything in Format render digits throughIntl, so a German dialog reads1,5 MB.
Guards, not exceptions
Most of these run inside a computed, where throwing takes the page down. So they return a sensible value instead:
| Case | What happens |
|---|---|
clamp(v, min, max) with min > max | Returns min. |
chunk(items, 0) | Size is coerced to at least 1, rather than looping forever. |
percentage(n, 0) | Returns 0. A NaN CSS width silently draws a full bar. |
sumBy over a row that yields NaN | That row is skipped, so one bad value cannot poison a total. |
move(items, from, to) out of range | Returns the list unchanged, rather than dropping or duplicating. |
get(obj, 'a.b.c') through a missing link | Returns undefined, or your fallback. |
Try them
Every group page carries a live playground, running the template's own helpers. Change an input and the result follows.
Where to go next
- Array — the most used of the nine.
- Format — dates, money and compact figures.
- Internationalization — where the active locale comes from.