reso-ui
v3.0.0
Published
Reusable React components in Typescript
Readme
Reso UI
A React component library built with TypeScript + SCSS. Ships as tree-shakeable ESM + CJS output via Rollup.
Storybook: reso-ui-storybook.s3-website-ap-southeast-1.amazonaws.com
Guiding Principles
Customizability-first — Clients can and should be able to override most if not all units of a UI component. Override the entire palette via
<ResoUiProvider theme={...}>, or override colours and behaviour directly on individual components via props. Clients should NOT override styles by hacky CSS means (e.g. directly overwriting compiled CSS class names in their projects).Clean and minimalistic — We eliminate clutter and prioritize ease of interaction for app users AND ease of component use for developers. Maximum configurability, yet minimal complication. Easy. Simple. Minimal.
In-house solutions — We build everything ourselves (drag and drop, date selector calculations, etc). No dependencies on third-party component libraries unless absolutely necessary.
Performant and zero runtime cost — Theming via CSS custom properties; no theme object traversal at render time.
Table of Contents
- Using the Library (Client Setup)
- Theming & Dark Mode Setup
- Overriding the Color Palette
- Utility Classes
- Dev Notes
- CI/CD Notes
Using the Library (Client Setup)
Requirements
- Node 18+
- React
^18.2 - React Dom
^18.2 - classnames
^2.3(peer dependency — install it if your project doesn't already have it)
Install
npm install reso-ui
# peer deps (if not already installed)
npm install classnamesFor the latest alpha (v3 pre-release):
npm install reso-ui@alphaFor the latest stable v2 release:
npm install [email protected]Client project files
custom.d.ts (project root)
Create this file so TypeScript knows how to handle SVG and font file imports:
// custom.d.ts
declare module "*.svg" {
import React from "react";
const SVG: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default SVG;
}
declare module "*.ttf" {
const content: string;
export default content;
}Then add it to your tsconfig.json:
{
"compilerOptions": { ... },
"include": ["src", "custom.d.ts"]
}Bundler Setup
The library ships pre-compiled JS (ESM + CJS), a single CSS file, and the Poppins and DancingScript font files — all locally bundled with no external network dependency.
Pick the section that matches your bundler:
Vite
Tested with Vite 6.x (@vitejs/plugin-react 5.x).
Vite handles CSS imports and font asset resolution out of the box. No vite.config.ts changes are required for reso-ui to work.
Install the core Vite dependencies:
npm install -D vite@^6.4.2 @vitejs/plugin-react@^5.2.0If your project uses its own SCSS files, install the sass package:
npm install -D sass@^1.87.0If your project imports .svg files as React components, add vite-plugin-svgr:
npm install -D vite-plugin-svgr@^5.2.0// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";
export default defineConfig({
plugins: [react(), svgr()],
});That's it — import "reso-ui/styles" works immediately since Vite natively processes CSS from node_modules and resolves the relative url(fonts/...) paths in the stylesheet.
Webpack
Tested with Webpack 5.x (matches the Reso online-store and web-storefront projects).
Your webpack config needs:
- CSS loader — to handle
import "reso-ui/styles" - asset modules — to resolve the
.ttffont files referenced in the library CSS
Install the required loaders:
npm install -D webpack@^5.99.6 webpack-cli@^6.0.1 webpack-dev-server@^5.2.1 \
ts-loader@^9.5.2 style-loader@^4.0.0 css-loader@^7.1.2 \
sass-loader@^16.0.5 sass@^1.87.0 \
html-webpack-plugin@^5.6.3Minimum webpack rules:
// webpack.config.js
module.exports = {
module: {
rules: [
// Handle CSS (from reso-ui and your own styles)
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// Handle SCSS (your own styles only — reso-ui ships pre-compiled CSS)
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
// Required: resolve font files bundled with reso-ui
{
test: /\.(ttf|woff|woff2|eot)$/,
type: "asset/resource",
},
// TypeScript
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
};Fonts: Poppins (all weights) and DancingScript are bundled inside the npm package under
dist/fonts/. Theasset/resourcewebpack rule lets webpack resolve the font paths fromdist/styles.cssautomatically — no CDN or manual font installation required.
Next.js
Tested with Next.js 15.x (App Router and Pages Router).
Next.js handles CSS imports, font asset resolution, and TypeScript out of the box. No next.config changes are required for reso-ui to work.
npm install next@^15.5.18 react@^18.2.0 react-dom@^18.2.0
npm install reso-ui classnamesIf your project uses its own SCSS files:
npm install -D sass@^1.87.0If your project imports .svg files as React components, configure @svgr/webpack in next.config:
// next.config.mjs
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
};
export default nextConfig;npm install -D @svgr/webpack@^8.1.0App Router setup
Reso UI components use React context and hooks internally, so they require a "use client" boundary. Create a client-side provider wrapper:
// src/providers/ResoUiClientProvider.tsx
"use client";
import { ResoUiProvider } from "reso-ui";
export const ResoUiClientProvider = ({ children }: { children: React.ReactNode }) => (
<ResoUiProvider mode="system">
{children}
</ResoUiProvider>
);Then import the styles and provider in your root layout:
// src/app/layout.tsx
import "reso-ui/styles";
import "./globals.css";
import { ResoUiClientProvider } from "@/providers/ResoUiClientProvider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<ResoUiClientProvider>
{children}
</ResoUiClientProvider>
</body>
</html>
);
}Use reso-ui components in any Client Component:
// src/app/dashboard/page.tsx
"use client";
import { Button, Text, View } from "reso-ui";
export default function DashboardPage() {
return (
<View pt={9} ph={4}>
<Text Element="h1">Dashboard</Text>
<Button text="Click me" onClick={() => alert("clicked")} />
</View>
);
}Pages Router setup
Import styles and wrap with the provider in _app.tsx:
// pages/_app.tsx
import "reso-ui/styles";
import "../styles/globals.css";
import { ResoUiProvider } from "reso-ui";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return (
<ResoUiProvider mode="system">
<Component {...pageProps} />
</ResoUiProvider>
);
}Note: Reso UI does not ship
"use client"directives. In the App Router, all pages and components that use reso-ui components must be Client Components (marked with"use client"), or the reso-ui components must be wrapped in a Client Component.
Create React App (CRA)
Tested with react-scripts 5.x.
CRA handles CSS imports, font asset resolution, and SVG imports out of the box. No configuration changes are required.
npx create-react-app my-app --template typescript
cd my-app
npm install reso-ui classnamesIf your project uses its own SCSS files, install the sass package:
npm install -D sass@^1.87.0Everything else works immediately — CRA's built-in webpack config already includes CSS loaders, asset modules for fonts, and @svgr/webpack for SVG imports.
Provider setup
Wrap your application root with ResoUiProvider. It provides theming and responsive context to all Reso UI components.
// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { ResoUiProvider } from "reso-ui";
import "reso-ui/styles";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<ResoUiProvider>
<App />
</ResoUiProvider>
);Import styles
Import the Reso UI stylesheet before your own application styles so your custom styles can override them:
// index.tsx (or index.ts / index.js)
import "reso-ui/styles"; // ← reso-ui first
import "./index.css"; // ← your app styles afterBasic usage
import { Button, Banner, Text } from "reso-ui";
export const MyPage = () => (
<>
<Banner type="info" text="Welcome to My App!" />
<Text Element="h1">Hello World</Text>
<Button text="Click me" onClick={() => alert("clicked")} />
</>
);All component props and usage examples are documented in the Storybook instance.
Theming & Dark Mode Setup
Reso UI supports light mode, dark mode, and system-preference-based theming out of the box. Theme switching is controlled via the mode prop on ResoUiProvider.
Quick setup
// App.tsx
import React, { useState } from "react";
import { ResoUiProvider, useTheme } from "reso-ui";
import "reso-ui/styles";
const ThemeToggle = () => {
const { resolvedMode, setMode } = useTheme();
return (
<button onClick={() => setMode(resolvedMode === "light" ? "dark" : "light")}>
Switch to {resolvedMode === "light" ? "dark" : "light"} mode
</button>
);
};
const App = () => (
<ResoUiProvider mode="system">
<ThemeToggle />
{/* your app */}
</ResoUiProvider>
);Mode options
| Mode | Behaviour |
|------|-----------|
| "light" (default) | Light theme applied |
| "dark" | Dark theme applied |
| "system" | Follows the user's OS prefers-color-scheme preference and reacts to changes in real time |
How it works
ResoUiProvider wraps your app with a ThemeProvider that sets the data-resoui-theme attribute and CSS custom properties on your #root element. All Reso UI component styles scope their dark-mode overrides to [data-resoui-theme="dark"], so switching modes is instantaneous with zero re-renders of component trees.
Using useTheme in your own components
import { useTheme } from "reso-ui";
const MyComponent = () => {
const { resolvedMode, setMode, theme } = useTheme();
// resolvedMode is always "light" or "dark" (resolved from "system" if needed)
// setMode("light" | "dark" | "system") to change the theme
// theme contains the current color and token overrides
return <div className={resolvedMode === "dark" ? "my-dark-style" : ""}>{/* ... */}</div>;
};Styling your own components for dark mode
Scope dark-mode overrides with the same attribute selector:
.my_component {
background: white;
color: #333;
}
[data-resoui-theme="dark"] .my_component {
background: #1e1e1e;
color: #e0e0e0;
}Overriding the Color Palette
ResoUiProvider accepts optional theme and mode props for runtime theming with no build step required.
Light / dark mode
// Light (default)
<ResoUiProvider>…</ResoUiProvider>
// Dark
<ResoUiProvider mode="dark">…</ResoUiProvider>
// Follow OS preference (prefers-color-scheme)
<ResoUiProvider mode="system">…</ResoUiProvider>Override palette semantics
Pass a partial theme.colors object to override semantic color roles. Keys map to CSS custom properties on --resoui-*.
<ResoUiProvider
theme={{
colors: {
brandPrimary: "#1a365d", // primary brand (navy)
brandSecondary: "#c53030", // secondary brand (coral/CTA)
utilityError: "#e53e3e",
utilitySuccess: "#276749",
},
}}
>
<App />
</ResoUiProvider>Full colors keys (ResoPaletteSemantics):
| Key | CSS variable |
|-----|-------------|
| brandPrimaryGhost | --resoui-brand-primary-ghost |
| brandPrimaryLight | --resoui-brand-primary-light |
| brandPrimaryMedium | --resoui-brand-primary-medium |
| brandPrimary | --resoui-brand-primary |
| brandPrimaryDark | --resoui-brand-primary-dark |
| brandPrimaryDarkest | --resoui-brand-primary-darkest |
| brandSecondaryPale | --resoui-brand-secondary-pale |
| brandSecondaryLight | --resoui-brand-secondary-light |
| brandSecondary | --resoui-brand-secondary |
| brandSecondaryDark | --resoui-brand-secondary-dark |
| brandSecondaryDarker | --resoui-brand-secondary-darker |
| brandSecondaryGradient | --resoui-brand-secondary-gradient |
| brandTertiaryPale | --resoui-brand-tertiary-pale |
| brandTertiaryLight | --resoui-brand-tertiary-light |
| brandTertiary | --resoui-brand-tertiary |
| brandTertiaryDark | --resoui-brand-tertiary-dark |
| brandTertiaryDarker | --resoui-brand-tertiary-darker |
| brandQuaternaryPale | --resoui-brand-quaternary-pale |
| brandQuaternaryLight | --resoui-brand-quaternary-light |
| brandQuaternary | --resoui-brand-quaternary |
| brandQuaternaryDark | --resoui-brand-quaternary-dark |
| brandQuaternaryDarker | --resoui-brand-quaternary-darker |
| utilitySuccessBg | --resoui-utility-success-bg |
| utilitySuccessLight | --resoui-utility-success-light |
| utilitySuccess | --resoui-utility-success |
| utilitySuccessDark | --resoui-utility-success-dark |
| utilityWarningBg | --resoui-utility-warning-bg |
| utilityWarningLight | --resoui-utility-warning-light |
| utilityWarning | --resoui-utility-warning |
| utilityWarningDark | --resoui-utility-warning-dark |
| utilityErrorBg | --resoui-utility-error-bg |
| utilityErrorLight | --resoui-utility-error-light |
| utilityError | --resoui-utility-error |
| utilityErrorDark | --resoui-utility-error-dark |
| utilityInfoBg | --resoui-utility-info-bg |
| utilityInfoLight | --resoui-utility-info-light |
| utilityInfo | --resoui-utility-info |
| utilityInfoDark | --resoui-utility-info-dark |
| neutralLightest | --resoui-neutral-lightest |
| neutralLight | --resoui-neutral-light |
| neutralMedium | --resoui-neutral-medium |
| neutralDark | --resoui-neutral-dark |
| neutralDarker | --resoui-neutral-darker |
| neutralOverlay | --resoui-neutral-overlay |
| baseWhite | --resoui-base-white |
| baseBlack | --resoui-base-black |
Override spacing, typography, and layout tokens
<ResoUiProvider
theme={{
tokens: {
fontFamily: "'Inter', sans-serif",
radiusDefault: "4px",
radiusLg: "8px",
spacing4: "20px",
},
}}
>
<App />
</ResoUiProvider>Full tokens keys (ResoTokens):
| Key | CSS variable | Default |
|-----|-------------|---------|
| spacing0 … spacing9 | --resoui-spacing-0 … --resoui-spacing-9 | 0px … 96px |
| fontFamily | --resoui-font-family | "Poppins", "Roboto", sans-serif |
| fontSize1 … fontSize9 | --resoui-font-size-1 … --resoui-font-size-9 | 10px … 26px |
| fontWeightNormal | --resoui-font-weight-normal | 400 |
| fontWeightMedium | --resoui-font-weight-medium | 500 |
| fontWeightSemibold | --resoui-font-weight-semibold | 600 |
| fontWeightBold | --resoui-font-weight-bold | 700 |
| radiusSm | --resoui-radius-sm | 4px |
| radiusDefault | --resoui-radius-default | 8px |
| radiusLg | --resoui-radius-lg | 16px |
| radiusFull | --resoui-radius-full | 9999px |
| shadowSm | --resoui-shadow-sm | 0 1px 3px rgba(0,0,0,0.1) |
| shadowDefault | --resoui-shadow-default | 0px 2px 15px rgba(34,60,102,0.2) |
| shadowLg | --resoui-shadow-lg | 0px 4px 25px rgba(34,60,102,0.15) |
| transitionFast | --resoui-transition-fast | 0.125s |
| transitionNormal | --resoui-transition-normal | 0.25s |
| transitionSlow | --resoui-transition-slow | 0.5s |
Utility Classes
Reso UI ships utility CSS classes that consumer projects can apply directly via className or rootClassName. These are included in the reso-ui/styles stylesheet and support dark mode automatically where applicable.
Quick Reference
| Class | Category | Dark Mode | Description |
|-------|----------|-----------|-------------|
| resoui_section_border | Layout | Yes | Bordered section container with default border-radius |
| resoui_table_base | Tables | Yes | Base <table> styling (font, collapse, full width) |
| resoui_th_base | Tables | Yes | Table header cell (padding, border, font-weight) |
| resoui_tr_base | Tables | Yes | Table row (border, hover highlight) |
| resoui_tr_base_selected | Tables | Yes | Selected table row highlight |
| resoui_td_base | Tables | No | Table data cell (padding) |
| resoui_navItem_base | Navigation | No | Nav item base layout for custom NavItem renders |
| resoui_dropdown_option_base | Forms | Yes | Dropdown option base for custom DropdownSelect options |
| resoui_no_select | General | No | Disable text selection |
| resoui_component_disabled | General | No | Disabled overlay with pointer-events disabled |
| resoui_background_image | General | No | Background image with cover |
| resoui_truncate | Text | No | Truncate text with ellipsis |
| resoui_text_no_block_margin | Text | No | Remove default block margins |
| resoui_text_no_inline_margin | Text | No | Remove default inline margins |
Section Border
A bordered container for card-like content sections. Apply via rootClassName on Flex or View.
import { Flex, Text } from "reso-ui";
<Flex direction="column" rootClassName="resoui_section_border" pa={5}>
<Text Element="h3">Section Title</Text>
<Text>Section content goes here.</Text>
</Flex>Light mode: 1px solid neutral border + default border-radius. Dark mode: border color automatically darkens.
Tables
Base styles for native HTML <table> elements. Apply via the className attribute. Dark mode is handled automatically.
| Class | Apply to | What it does |
|-------|----------|-------------|
| resoui_table_base | <table> | Font-family, border-collapse, full width, font size |
| resoui_th_base | <th> | Padding, bottom border, font-weight 500, left-aligned |
| resoui_tr_base | <tr> | Bottom border, hover background highlight |
| resoui_tr_base_selected | <tr> | Selected row highlight (combine with resoui_tr_base) |
| resoui_td_base | <td> | Cell padding |
<table className="resoui_table_base">
<thead>
<tr>
<th className="resoui_th_base">Name</th>
<th className="resoui_th_base">Email</th>
<th className="resoui_th_base">Role</th>
</tr>
</thead>
<tbody>
<tr className="resoui_tr_base">
<td className="resoui_td_base">John Doe</td>
<td className="resoui_td_base">[email protected]</td>
<td className="resoui_td_base">Admin</td>
</tr>
</tbody>
</table>For selected rows, combine both classes:
<tr className={`resoui_tr_base ${selectedId === item.id ? "resoui_tr_base_selected" : ""}`}>Add custom column-specific styles alongside the utility classes:
<th className="resoui_th_base myPage_th_right">Amount</th>
<td className="resoui_td_base myPage_td_mono">$120.00</td>.myPage_th_right { text-align: right; }
.myPage_td_mono { font-family: monospace; font-size: 12px; }Nav Item Base
Base layout class for custom nav items rendered inside NavItem via renderCustomNavItem. Provides standard sizing consistent with the Navbar.
import { NavItem } from "reso-ui";
import { Link, useLocation } from "react-router-dom";
const AppNavItem = ({ to, children }) => {
const { pathname } = useLocation();
return (
<NavItem
renderCustomNavItem={() => (
<Link
to={to}
className={`navItem_base ${pathname === to ? "my_nav_active" : ""}`}
>
{children}
</Link>
)}
>
{children}
</NavItem>
);
};Dropdown Option Base
Base class for custom dropdown options rendered inside DropdownSelect. Provides consistent sizing, padding, and dark mode text color.
import { DropdownSelect } from "reso-ui";
import { Link } from "react-router-dom";
<DropdownSelect label="Account">
<Link to="/profile" className="resoui_dropdown_option_base">My Profile</Link>
<Link to="/settings" className="resoui_dropdown_option_base">Settings</Link>
</DropdownSelect>General Utilities
| Class | What it does |
|-------|-------------|
| resoui_no_select | Prevent text selection (all browser prefixes) |
| resoui_component_disabled | Semi-transparent overlay + disable pointer events |
| resoui_background_image | Background image: cover, centered, no-repeat |
<Flex rootClassName={isLocked ? "resoui_component_disabled" : ""}>
<Text>This section is locked</Text>
</Flex>Text Utilities
| Class | What it does |
|-------|-------------|
| resoui_truncate | Truncate with ellipsis (overflow: hidden; text-overflow: ellipsis; white-space: nowrap) |
| resoui_text_no_block_margin | Remove block margins from headings (margin-block-start: 0) |
| resoui_text_no_inline_margin | Remove inline margins |
<Text rootClassName="resoui_truncate" rootStyles={{ maxWidth: "200px" }}>
Very long product name that should be truncated
</Text>Dev Notes
Available commands
| Command | What it does |
|---------|-------------|
| npm start | Webpack dev server → serves src/App.tsx at http://localhost:3000 |
| npm run storybook | Storybook dev server at http://localhost:6006 |
| npm run build | Rollup production build → dist/esm/, dist/cjs/, dist/styles.css |
| npm run build-storybook | Static Storybook build → storybook-static/ |
| npm test | Jest unit tests (all 65 suites) |
| npm run test-coverage | Jest with coverage report → coverage/ |
| npm run lint | TSLint check (excludes stories and test files) |
Viewing the App.tsx sandbox
npm start starts a Webpack dev server that hot-reloads src/App.tsx. This file is a scratchpad for manually testing components during development — it is not the library entry point.
npm start
# → http://localhost:3000App.tsx is already wrapped with <ResoUiProvider> in src/index.tsx, so all components render with the default theme.
Running Storybook
npm run storybook
# → http://localhost:6006Storybook is the primary UI for browsing all components, their props, and usage examples.
Library entry point
The library exports everything from src/library/index.ts. When you run npm run build, Rollup compiles this to:
dist/
esm/ ← tree-shakeable ES modules (one file per component)
cjs/ ← CommonJS bundle
styles.css ← single compiled stylesheet for all componentsdist/ is not committed to git — it is built fresh in CI before every npm publish.
CI/CD Notes
The pipeline is defined in .github/workflows/ci-cd.yml.
What happens on every push / PR
- Lint (
npm run lint) - Unit tests (
npm run test-coverage) on Node 18 and Node 20 - Library build (
npm run build) - Storybook build (
npm run build-storybook)
PRs to main, 2.x, v3, or beta must pass all four steps.
Branch → npm dist-tag mapping
| Branch | npm dist-tag | Version format | Example |
|--------|-------------|----------------|---------|
| main | @latest | x.0.0 | 3.1.0 |
| v3 | @alpha | 3.0.0-alpha.n | 3.0.0-alpha.5 |
| beta | @beta | x.0.0-beta.n | 3.1.0-beta.2 |
| 2.x | @2.x | 2.x.x | 2.44.0 |
How to make a change and publish a new version
Check out
main(orv3for alpha work):git checkout main # for stable releases # or git checkout v3 # for alpha pre-releasesMake your changes, commit using Conventional Commits:
git add . git commit -m "feat: add Tooltip component" # or: fix: correct Button disabled opacity # or: chore: update dependenciesCommit type determines the version bump:
fix:→ patch (3.0.1)feat:→ minor (3.1.0)feat!:orBREAKING CHANGE:footer → major (4.0.0)
Push — CI runs automatically. If all checks pass,
semantic-releasedetermines the next version, publishes to npm, and commits the updatedCHANGELOG.mdandpackage.jsonback to the branch.git push origin mainPull the release commit after CI finishes (semantic-release pushes a version bump commit back):
git pull origin main
How to publish alpha (pre-release) versions
Work on the v3 branch. Every push that contains a fix: or feat: commit automatically publishes a new 3.0.0-alpha.n to the @alpha dist-tag.
git checkout v3
# make changes
git commit -m "feat: add Paginator component"
git push origin v3
# → publishes 3.0.0-alpha.5 to npm @alphaClients can install alpha builds:
npm install reso-ui@alphaHow to publish beta versions
Push to the beta branch. Follows the same Conventional Commit rules; versions are tagged @beta.
git checkout beta
git merge v3 # merge alpha work into beta for wider testing
git push origin beta
# → publishes 3.0.0-beta.1 to npm @betaHow to create a breaking change (new major version)
Use the BREAKING CHANGE footer in your commit message or add ! after the type:
# Method 1 — exclamation mark
git commit -m "feat!: remove theme prop from all components"
# Method 2 — footer
git commit -m "feat: remove theme prop from all components
BREAKING CHANGE: The theme prop has been removed from all components.
Wrap your app in <ResoUiProvider mode=\"dark\"> instead."When pushed to main, this bumps the major version (e.g. 3.0.0 → 4.0.0).
Before merging a major version to main, create a maintenance branch for the previous major so it can still receive patch releases:
# Preserve current main as a maintenance line before bumping major
git checkout main
git checkout -b 3.x # create 3.x maintenance branch
git push origin 3.x
# Add "3.x" to release.config.js branches array, then push major change to mainMaintaining an older major (e.g. v2 patches)
The 2.x branch receives security fixes and critical patches without merging v3 changes.
git checkout 2.x
git commit -m "fix: correct date selector boundary validation"
git push origin 2.x
# → publishes 2.44.1 to npm @2.xClients pinned to v2 install patches with:
npm install [email protected]