Saltar al contenido

Para developers

Beta

Paso 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-proyecto

Cuando 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.json

No hace preguntas. Deja tres archivos y no toca ningún otro:

ArchivoQué es
components.jsonLa configuración que el CLI de shadcn necesita para instalar componentes.
styles/tokens.cssLas variables del sistema: colores por rol (claro y oscuro), tipografía, espaciado, radius, elevación y motion.
styles/polen.cssConecta 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.json

3. 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-themes

Creá 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.json

El 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 dev

En 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);
}
FamiliaUtilidad / variableGuía
Colorbg-* text-* border-* con nombre de rolColor semántico
Tipografíatext-sm … text-heading-lg … text-display-xl, font-mediumTipografía
Espaciadovar(--space-*)Espaciado
Radiusrounded-xxs … rounded-4xlRadius
Elevaciónshadow-xs … shadow-2xlElevación
Tamañosvar(--ds-control-*), var(--ds-icon-*)Tamaños
Motionease-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, ni px mágicos. Si falta un token, se pide.
  • No pises estilos del componente con className más allá de layout (margen, ancho). Si necesitás otra apariencia, falta una variante.
  • Accesibilidad no es opcional: respetá el focus ring, poné aria-label en los icon-only, no comuniques solo con color.