Setup
Four steps, and only the first two are required. Nothing here is specific to a component — do it once.
Install
react, react-dom and motion are peer dependencies, so they resolve to whatever versions your app already has.
npm i @mihirmodi/ui motionTokens
The components are Tailwind v4 utilities over a set of CSS custom properties. Import the tokens after Tailwind, then point Tailwind at the package — Tailwind only generates the utilities it can see, and these live in another package. Skipping that second line ships the dock with no styles at all, which is the single most common way this goes wrong.
@import "tailwindcss";
@import "@mihirmodi/ui/tokens.css";
/* Tailwind only generates the utilities it can see, and the components
live in another package — point it at them or they ship unstyled. */
@source "../node_modules/@mihirmodi/ui/dist";Every value is a custom property, so re-colouring is a redeclaration in your own :root rather than a fork.
Theme
Dark mode keys off data-theme on <html>, not prefers-color-scheme: a reader who has chosen light on your site should get light on a machine set to dark, and a media query cannot express a choice.
Set it with an inline, blocking script in the head. This is the one place a blocking script earns its cost — anything deferred runs after the first paint, and the reader sees a white flash before their dark theme arrives.
<!-- Set data-theme on <html>. Inline and blocking, in <head>: any
deferred script runs after the first paint, and the reader sees a
white flash before their dark theme arrives. -->
<script>
try {
document.documentElement.dataset.theme =
localStorage.getItem("theme") === "dark" ? "dark" : "light";
} catch (e) {
document.documentElement.dataset.theme = "light";
}
</script>Routing (optional)
Without this the components render plain anchors, which navigate correctly but reload the page. Rather than depend on a router — which would tie the package to one framework — the dependency is inverted: you hand in the link component you already use.
"use client";
import Link from "next/link";
import { UIProvider } from "@mihirmodi/ui";
// Optional. Without it the components render plain <a> tags, which
// navigate correctly but reload the page.
//
// "use client" is not decoration here: UIProvider takes a COMPONENT, and
// a component is a function, which cannot be passed across the server /
// client boundary as a prop. So the client side has to import Link
// itself. Render <Providers> from your server layout.
export function Providers({ children }) {
return <UIProvider link={Link}>{children}</UIProvider>;
}