Instalação
Niemeyer é distribuído como um pacote npm privado no GitHub Packages, construído sobre Tailwind 4. Os seis passos numerados abaixo levam de um app vazio até um <Button> funcionando.
1. Configurar acesso ao GitHub Packages
Crie um Personal Access Token (clássico) no GitHub com read:packages e exporte como GITHUB_TOKEN. Em seguida, adicione este .npmrc na raiz do projeto (Yarn Classic lê direto; usuários de Yarn Berry podem configurar o equivalente npmScopes em .yarnrc.yml):
# .npmrc — at project root
@morada-ai:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
2. Instalar o pacote e suas peers
Tailwind 4 e @tabler/icons-react são peer dependencies — instale no seu app para que uma única cópia seja hoisted ao lado do pacote.
# 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-react3. Configurar PostCSS
Tailwind 4 publica seu plugin PostCSS separadamente. Adicione-o ao seu config (não precisa de autoprefixer):
// postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};4. Conectar a folha de estilos
Três @imports explícitos em app/globals.css, nesta ordem. Você só precisa de um @source para o código do seu app — o theme.css do pacote já faz self-source dos próprios componentes, e o Tailwind 4 acumula diretivas @source de todas as folhas importadas.
/* 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}";5. Carregar fontes
Os design tokens do pacote referenciam Outfit (títulos) e Lato (corpo) apenas pelo nome da família — você escolhe como entregar os arquivos de fonte. Três opções abaixo, em ordem de recomendação.
Opção A — <link> carregado pelo browser (recomendado; funciona em todo lugar)
Adicione no <head> do seu layout (ou via @font-face). O browser busca o CSS em runtime, então o servidor de build nunca precisa de acesso à internet para o Google Fonts. Trade-off: pequeno flash de texto não estilizado (FOUT) no primeiro paint enquanto o CSS resolve; com display=swap não há 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"
/>Opção B — next/font/google (apps Next.js com acesso à internet em build time)
Se o ambiente de build alcança fonts.googleapis.com, next/font/google faz self-host dos WOFF2 em build time, eliminando a requisição runtime ao Google. Atenção: o **build falha** em qualquer ambiente sem acesso ao Google Fonts (VPN corporativa, CI sandboxado, runners air-gapped) — use a Opção A nesses casos.
// 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>
);
}Em seguida, conecte os tokens do niemeyer às CSS variables do next/font no seu stylesheet, DEPOIS de @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;
}Opção C — Self-host
Coloque Outfit-*.woff2 e Lato-*.woff2 em public/fonts/ e declare regras @font-face no seu stylesheet. Zero dependência externa, mas você assume o pipeline de assets.
/* 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-exportar `cn()` para uso local no consumer
O pacote usa o mesmo padrão de cn do shadcn (clsx + tailwind-merge). Os dois helpers já vêm bundled, então uma re-exportação de uma linha basta:
// src/lib/utils.ts
export { cn } from "@morada-ai/niemeyer/utils";Opcional: ativar o plugin ESLint
Pega violações do design system automaticamente (cores hardcoded em className="…", imports de lucide-react, <button>/<input> nativos):
// eslint.config.js
const niemeyer = require("@morada-ai/niemeyer/eslint");
module.exports = [niemeyer.configs.recommended];