Mantle
Mantle is ngrok’s UI library and design system that powers its front-end.
Overview
Mantle is a carefully designed system of React components and utilities that establishes a unified design language and consistent user experience across ngrok’s web applications. Built with flexibility, extensibility, and developer ergonomics in mind, Mantle prioritizes accessibility, performance, and long-term maintainability. Developed in TypeScript, it offers strong typing, rich IDE support, and increased confidence through compile-time safety. Mantle empowers ngrok’s developers to craft interfaces that align seamlessly with the company’s brand, design principles, and engineering standards. By progressively enhancing standard DOM elements, it not only improves usability and accessibility, but also fills functional gaps—providing a robust foundation for building cohesive, modern UIs throughout the platform.
All of Mantle’s components are styled using Tailwind. and we compose around the following unstyled primitive component libraries:
Mantle uses Phosphor Icons as the primary icon library, providing a versatile and consistent set of icons. In addition, custom-designed icons tailored to ngrok’s needs are available through the @ngrok/mantle/icons
module.
Status
Mantle is a work in progress that’s currently adding components. It intends to replace new and existing ngrok user interfaces.
Mantle is available in its alpha state on NPM. It is open source and available on GitHub.
Setup
I want to use mantle
in my application…
Installation
Start by installing @ngrok/mantle
and all of the required peerDependencies
:
Mantle supports react
and react-dom
versions 18 and 19.
mantle and dependencies installation
pnpm add -E @ngrok/mantle @phosphor-icons/react date-fns react react-dom
You will also need to install the following devDependencies
:
mantle devDependencies installation
pnpm add -DE tailwindcss @tailwindcss/vite
Application Scaffolding
We need to add the @tailwindcss/vite
plugin to your Vite configuration.
vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import tailwindcss from "@tailwindcss/vite"; // import tailwindcss vite plugin
export default defineConfig({
plugins: [
tailwindcss(), // add tailwindcss plugin
reactRouter(),
tsconfigPaths(),
],
});
Then, in your react-router app’s src/root.tsx
file, import the mantle.css
file to apply the mantle styles.
We will also add the Theme Provider, Toaster, and Tooltip Provider to your app to enable theme selection, toasts, and tooltips.
It is critical to include the MantleThemeHeadContent
in the head
of your app to prevent a flash of unstyled content (FOUC). This component will inject the necessary script to prevent the FOUC.
app/root.tsx
import {
MantleThemeHeadContent,
ThemeProvider,
useInitialHtmlThemeProps,
} from "@ngrok/mantle/theme-provider";
import { Toaster } from "@ngrok/mantle/toast";
import { TooltipProvider } from "@ngrok/mantle/tooltip";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import "@ngrok/mantle/mantle.css"; // 👈 add this import to include mantle styles!
export function Layout({ children }: { children: React.ReactNode }) {
const initialHtmlThemeProps = useInitialHtmlThemeProps({
className: "h-full",
});
return (
<html {...initialHtmlThemeProps} lang="en-US" dir="ltr">
<head>
<meta charSet="utf-8" />
{/* 👇 The MantleThemeHeadContent should be rendered at the top of your <head>
to prevent a flash of unstyled content (FOUC)! */}
<MantleThemeHeadContent />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<ThemeProvider>
<TooltipProvider>
<Toaster />
{children}
</TooltipProvider>
</ThemeProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
You are now ready to use mantle components in your application! For example, you can use the Button!