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
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
You will also need to install the following devDependencies
:
Mantle only supports tailwindcss
version 3 at this time. We are in the process of upgrading to version 4.
mantle devDependencies installation
pnpm add -DE tailwindcss@3.4.1 postcss autoprefixer
Tailwind Configuration
Then, add the tailwind preset and mantle content to your tailwind configuration:
import { createRequire } from "node:module";
import { mantlePreset, resolveMantleContentGlob } from "@ngrok/mantle/tailwind-preset";
import type { Config } from "tailwindcss";
const require = createRequire(import.meta.url);
export default {
presets: [mantlePreset],
content: [resolveMantleContentGlob(require), "./app/**/*.tsx"], // 👈 don't forget to swap out app content glob here!
// ... the rest of your tailwind config!
} satisfies Config;
Application Scaffolding
I want to use mantle
in my application…
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!