Para developers
BetaPaso a paso para usar los tokens y los componentes de Polen en un proyecto Next.js con Tailwind, desde cero.
Polen se distribuye como código que copiás a tu proyecto (modelo registry, estilo shadcn), no como un paquete npm: el componente queda en tu repo, lo podés leer y ajustar, y no hay versiones que pelear.
Esta guía arranca de cero con Next.js y termina con un botón verde en pantalla. Si ya tenés un proyecto con Tailwind, saltá al paso 2.
0. Antes de empezar
- Node.js 20 o más nuevo.
- La fuente PP Neue Montreal (licenciada — pedila al equipo de diseño). Si todavía no la tenés, no te frena: todo funciona con la fuente del sistema hasta que la agregues en el paso 4.
1. Crear el proyecto
npx create-next-app@latest mi-proyecto
cd mi-proyectoCuando pregunte, respondé Sí a TypeScript, a Tailwind CSS y al App
Router, No a la carpeta src/, y dejá el alias de imports en @/*. Eso
deja Tailwind 4 instalado y configurado; no hay nada más que instalar.
2. Preparar el proyecto para Polen
Una sola vez por proyecto, desde la raíz:
npx shadcn@latest init https://polen.cdc.cool/r/init.jsonNo hace preguntas. Deja tres archivos y no toca ningún otro:
| Archivo | Qué es |
|---|---|
components.json | La configuración que el CLI de shadcn necesita para instalar componentes. |
styles/tokens.css | Las variables del sistema: colores por rol (claro y oscuro), tipografía, espaciado, radius, elevación y motion. |
styles/polen.css | Conecta esas variables con Tailwind (text-md, text-heading-lg, rounded-lg, shadow-lg, ease-enter…) y trae los estilos base: fondo, texto, fuente y anillo de foco. |
Si tu proyecto ya tiene components.json, instalá solo los estilos. El
mismo comando sirve para actualizarlos más adelante:
npx shadcn@latest add https://polen.cdc.cool/r/tokens.json3. Conectar los estilos
Abrí app/globals.css y reemplazá todo su contenido por estas dos líneas:
@import "tailwindcss";
@import "../styles/polen.css";create-next-app deja en ese archivo un :root con --background y
--foreground, un @theme con las fuentes Geist y un body en Arial. Hay
que borrarlos: pisan el fondo y la tipografía de Polen.
4. La tipografía
Creá la carpeta fonts/ en la raíz del proyecto y guardá ahí
PPNeueMontreal-Variable.woff2. Los tokens esperan la fuente en la variable
--font-pp-neue; la cargás con next/font/local en app/layout.tsx (el
archivo completo está en el paso siguiente):
import localFont from "next/font/local";
const ppNeue = localFont({
src: "../fonts/PPNeueMontreal-Variable.woff2",
variable: "--font-pp-neue",
weight: "100 900",
display: "swap",
});Sin ese paso, --font-pp-neue no existe y los tokens caen en la fuente del
sistema. Nada se rompe.
5. Dark mode
El tema se decide con el atributo data-theme en <html>, no con la
preferencia del sistema a secas. Lo maneja
next-themes:
npm install next-themesCreá components/theme-provider.tsx:
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ComponentProps } from "react";
export function ThemeProvider(props: ComponentProps<typeof NextThemesProvider>) {
return (
<NextThemesProvider
attribute="data-theme"
defaultTheme="system"
enableSystem
disableTransitionOnChange
{...props}
/>
);
}Y dejá app/layout.tsx así (fuente del paso 4 incluida):
import type { Metadata } from "next";
import localFont from "next/font/local";
import { ThemeProvider } from "@/components/theme-provider";
import "./globals.css";
const ppNeue = localFont({
src: "../fonts/PPNeueMontreal-Variable.woff2",
variable: "--font-pp-neue",
weight: "100 900",
display: "swap",
});
export const metadata: Metadata = { title: "Mi proyecto" };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html
lang="es"
suppressHydrationWarning
className={`${ppNeue.variable} h-full antialiased`}
>
<body className="flex min-h-full flex-col">
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}suppressHydrationWarning va en <html> porque data-theme se escribe en el
cliente. Para cambiar de tema desde cualquier componente cliente:
const { resolvedTheme, setTheme } = useTheme();
setTheme(resolvedTheme === "dark" ? "light" : "dark");No escribas dark: para colores: los roles ya cambian solos. dark: queda
para los casos raros que no son color.
6. Instalar y usar un componente
Cada componente se instala con un comando y aterriza en components/ds/,
junto con lo que necesita (Select trae Input; los interactivos instalan
@base-ui/react):
npx shadcn@latest add https://polen.cdc.cool/r/button.jsonEl comando exacto está en la pestaña Código de cada componente. Para
verlo andar, reemplazá app/page.tsx por:
import { Button } from "@/components/ds/button";
export default function Home() {
return (
<main className="mx-auto flex w-full max-w-xl flex-col gap-(--space-3xl) p-(--space-4xl)">
<h1 className="text-heading-lg font-medium text-fg">Hola, Polen</h1>
<div className="rounded-2xl border border-border bg-bg-elevated p-(--space-4xl) text-md shadow-lg">
<p className="text-fg-muted">Una tarjeta armada solo con tokens.</p>
</div>
<div className="flex flex-wrap gap-(--space-md)">
<Button>Inscribirme</Button>
<Button variant="stroke">Ver programa</Button>
<Button variant="tonal">Tonal</Button>
<Button variant="text">Texto</Button>
</div>
</main>
);
}npm run devEn http://localhost:3000 tenés que ver el título en PP Neue, una tarjeta con
sombra y cuatro botones: el primero verde sólido. Si el fondo es blanco puro
y la fuente es Arial, quedó algo del globals.css original (paso 3).
En la página de cada componente tenés el playground (que genera el snippet),
la tabla de props y la correspondencia con las propiedades de Figma. El
estado de cada uno está en Estado de componentes.
Las páginas completas se instalan igual, en
app/<nombre>/page.tsx.
Ese mismo snippet está escrito como template de Code Connect para verlo en Dev Mode al seleccionar una instancia; se activa cuando el plan de Figma lo permita.
7. Usar los tokens en tu propio código
Como utilidades de Tailwind — el nombre es el del rol semántico:
<div className="rounded-2xl border border-border bg-bg-elevated p-(--space-4xl) shadow-lg">
<h3 className="text-lg font-medium text-fg">Inscripción abierta</h3>
<p className="text-sm text-fg-muted">Cierra el 30 de marzo.</p>
</div>O como variables CSS:
.banner {
background: var(--info-subtle);
border: var(--ds-border-sm) solid var(--info-border);
color: var(--info-text);
}| Familia | Utilidad / variable | Guía |
|---|---|---|
| Color | bg-* text-* border-* con nombre de rol | Color semántico |
| Tipografía | text-sm … text-heading-lg … text-display-xl, font-medium | Tipografía |
| Espaciado | var(--space-*) | Espaciado |
| Radius | rounded-xxs … rounded-4xl | Radius |
| Elevación | shadow-xs … shadow-2xl | Elevación |
| Tamaños | var(--ds-control-*), var(--ds-icon-*) | Tamaños |
| Motion | ease-enter, var(--ds-duration-*) | Motion |
Actualizar
Un componente instalado es un archivo tuyo: Polen no lo toca. Para traer la
última versión, repetí su comando con --overwrite (perdés los cambios
locales que le hayas hecho). Los estilos se actualizan con el comando de
tokens del paso 2. Qué cambió en cada versión está en el
changelog.
Reglas de la casa
- Nada de valores sueltos: ni
#hex, nipxmágicos. Si falta un token, se pide. - No pises estilos del componente con
classNamemás allá de layout (margen, ancho). Si necesitás otra apariencia, falta una variante. - Accesibilidad no es opcional: respetá el focus ring, poné
aria-labelen los icon-only, no comuniques solo con color.