DSNiemeyer
Language

Installation

Niemeyer ships as a private GitHub Packages npm package built around Tailwind 4. Six numbered steps below take you from a fresh app to a working <Button>.

1. Configure GitHub Packages access

Create a GitHub personal access token (classic) with read:packages and export it as GITHUB_TOKEN. Then add this .npmrc at the project root (Yarn Classic reads it directly; Yarn Berry users can set the equivalent npmScopes in .yarnrc.yml):

# .npmrc — at project root
@morada-ai:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

2. Install the package and peers

Tailwind 4 and @tabler/icons-react are peer dependencies — install them in your app so a single copy is hoisted next to the package.

# Install the package
yarn add @morada-ai/niemeyer

# Plus the peer dependencies (one copy in your app)
yarn add tailwindcss@^4
yarn add @tabler/icons-react

3. Configure PostCSS

Tailwind 4 ships its PostCSS plugin separately. Add it to your config (no autoprefixer needed):

// postcss.config.mjs
export default {
    plugins: {
        "@tailwindcss/postcss": {},
    },
};

4. Wire the stylesheet

Three explicit @imports in app/globals.css, in this order. You only need one @source for your own app code — the package's theme.css already self-sources its components, and Tailwind 4 merges @source directives across all imported stylesheets.

/* app/globals.css */ 
@import "tailwindcss";
@import "@morada-ai/niemeyer/styles";
@import "tw-animate-css";

/* Only your app code — the package self-sources its own components */
@source "./app/**/*.{ts,tsx}";

Order matters: tailwindcss must come first so the @theme blocks shipped inside the package are picked up.

5. Load fonts

The package's design tokens reference Outfit (headings) and Lato (body) by family name only — you choose how to deliver the actual font files. Three options below, in order of recommendation.

Option A — Browser-loaded <link> (recommended; works everywhere)

Add to your layout <head> (or via @font-face). The browser fetches the CSS at runtime, so the build server never needs network access to Google Fonts. Trade-off: small flash of unstyled text (FOUT) on first paint while the CSS resolves; with display=swap there is no layout shift.

<!-- in <head>, plus a preconnect for the WOFF2 host -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Lato:wght@300;400;700;900&display=swap"
  rel="stylesheet"
/>

Option B — next/font/google (Next.js apps with network access at build time)

If your build environment can reach fonts.googleapis.com, next/font/google self-hosts the WOFF2 files at build time, eliminating the runtime request to Google. Be aware the **build will fail** in any environment that cannot reach Google Fonts (corporate VPN, sandboxed CI, air-gapped runners) — switch to Option A there.

// app/layout.tsx
import { Outfit, Lato } from "next/font/google";

const outfit = Outfit({
    subsets: ["latin"],
    weight: ["400", "500", "600", "700"],
    variable: "--font-outfit",
    display: "swap",
});
const lato = Lato({
    subsets: ["latin"],
    weight: ["300", "400", "700", "900"],
    variable: "--font-lato",
    display: "swap",
});

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html className={`${outfit.variable} ${lato.variable}`}>
            <body>{children}</body>
        </html>
    );
}

Then bind the niemeyer tokens to the next/font CSS variables in your app stylesheet, AFTER @import "@morada-ai/niemeyer/styles":

/* app/globals.css — AFTER @import "@morada-ai/niemeyer/styles" */
:root {
    --font-heading: var(--font-outfit), system-ui, -apple-system, sans-serif;
    --font-body:    var(--font-lato),   system-ui, -apple-system, sans-serif;
}

Option C — Self-host

Drop Outfit-*.woff2 and Lato-*.woff2 into public/fonts/ and declare @font-face rules in your stylesheet. Zero external dependency, but you carry the asset pipeline yourself.

/* app/globals.css */
@font-face {
    font-family: "Outfit";
    src: url("/fonts/Outfit-Variable.woff2") format("woff2-variations");
    font-weight: 100 900;
    font-display: swap;
}
@font-face {
    font-family: "Lato";
    src: url("/fonts/Lato-Regular.woff2") format("woff2");
    font-weight: 400;
    font-display: swap;
}
/* Repeat @font-face for every Lato weight you ship (300, 700, 900). */

6. Re-export `cn()` so consumer code can import it locally

The package ships the same cn pattern as shadcn (clsx + tailwind-merge). Both helpers are bundled, so a one-line re-export is enough:

// src/lib/utils.ts
export { cn } from "@morada-ai/niemeyer/utils";

Optional: enable the ESLint plugin

Catches design-system violations automatically (no hardcoded colors in className="…", no lucide-react imports, no native <button>/<input>):

// eslint.config.js
const niemeyer = require("@morada-ai/niemeyer/eslint");

module.exports = [niemeyer.configs.recommended];