Empezar
Guías por framework
Where the stylesheet, the provider and the theme script go in Vite, Next.js, React Router, Astro and TanStack Start, and what each one needs from the client and the server.
La configuración son las mismas tres cosas en todas partes: importar @alea-design/ui/styles.css una vez, envolver la app en ThemeProvider y, cuando un servidor renderiza la página, añadir ThemeScript al head. Lo que cambia es el archivo donde va cada una. Cómo crear un proyecto e instalar los paquetes está en la Guía de instalación.
Vite
Una app solo de cliente: la página se construye en el navegador, así que no hay nada que renderizar en un servidor.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it once in the entry file, src/main.tsx. |
| El proveedor | Wrap the app in ThemeProvider where it is rendered. It reads the saved theme on the first render, so there is no flash after React starts. |
| Código de cliente | All of it: nothing needs a directive. |
| Renderizado en servidor | None, so ThemeScript is not needed. |
// src/main.tsximport '@alea-design/ui/styles.css';import { createRoot } from 'react-dom/client';import { ThemeProvider } from '@alea-design/ui';import { App } from './App';createRoot(document.getElementById('root')!).render(<ThemeProvider defaultTheme="system"><App /></ThemeProvider>,);
Un proyecto que funciona con esta configuración: examples/vite.
Next.js App Router
Componentes de servidor por defecto. Cada export de @alea-design/ui está marcado como componente de cliente, así que un componente de servidor puede renderizar componentes de Alea directamente.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it in the root layout, app/layout.tsx. |
| El proveedor | A small file marked "use client", app/providers.tsx, holds ThemeProvider; the layout stays a server component and renders it around the children. Props that are functions (navigate, useHref) have to be set in that client file. |
| Necesita "use client" | Any file of yours that uses a hook (useTheme, useState) or passes a function such as onPress. Components copied with the CLI are plain source files without the directive, so import them from a file that has it. |
| Renderizado en servidor | Put ThemeScript in the head of the root layout and suppressHydrationWarning on the html element, so the saved theme is on the page before the first paint. |
// app/layout.tsximport '@alea-design/ui/styles.css';import { ThemeScript } from '@alea-design/ui';import { Providers } from './providers';export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en" suppressHydrationWarning><head><ThemeScript /></head><body><Providers>{children}</Providers></body></html>);}
// app/providers.tsx'use client';import { ThemeProvider } from '@alea-design/ui';export function Providers({ children }: { children: React.ReactNode }) {return <ThemeProvider defaultTheme="system">{children}</ThemeProvider>;}
Un proyecto que funciona con esta configuración: examples/next.
Next.js Pages Router
Cada página es un componente de cliente que además se renderiza en el servidor, así que no interviene ninguna directiva.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it in pages/_app.tsx: Next.js allows global CSS from a package there. |
| El proveedor | Wrap the page component in ThemeProvider in pages/_app.tsx. |
| Código de cliente | All of it: nothing needs a directive. |
| Renderizado en servidor | Put ThemeScript inside Head in pages/_document.tsx and suppressHydrationWarning on Html. The repository has no Pages Router project; this setup was built and rendered on the server with the same packages. |
// pages/_app.tsximport '@alea-design/ui/styles.css';import type { AppProps } from 'next/app';import { ThemeProvider } from '@alea-design/ui';export default function App({ Component, pageProps }: AppProps) {return (<ThemeProvider defaultTheme="system"><Component {...pageProps} /></ThemeProvider>);}
// pages/_document.tsximport { Html, Head, Main, NextScript } from 'next/document';import { ThemeScript } from '@alea-design/ui';export default function Document() {return (<Html lang="en" suppressHydrationWarning><Head><ThemeScript /></Head><body><Main /><NextScript /></body></Html>);}
El ejemplo con App Router está en examples/next.
React Router
Modo framework con renderizado en el servidor activado (ssr: true en react-router.config.ts). Los componentes son React normal: sin directivas.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it in app/root.tsx. |
| El proveedor | In the default export of app/root.tsx, around the Outlet. Passing navigate and useHref makes Alea links move through the router without reloading the page. |
| Código de cliente | All of it: nothing needs a directive. |
| Renderizado en servidor | Put ThemeScript in the head of the Layout export of the same file, with suppressHydrationWarning on the html element. |
// app/root.tsximport '@alea-design/ui/styles.css';import { Links, Meta, Outlet, Scripts, useHref, useNavigate } from 'react-router';import { ThemeProvider, ThemeScript } from '@alea-design/ui';export function Layout({ children }: { children: React.ReactNode }) {return (<html lang="en" suppressHydrationWarning><head><Meta /><Links /><ThemeScript /></head><body>{children}<Scripts /></body></html>);}export default function App() {const navigate = useNavigate();return (<ThemeProvider defaultTheme="system" navigate={navigate} useHref={useHref}><Outlet /></ThemeProvider>);}
Un proyecto que funciona con esta configuración: examples/react-router.
Astro
Alea se ejecuta en islas de React. La página en sí es Astro; todo lo que dibuja Alea vive dentro de una isla.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it in the island component: Astro adds the CSS to the pages that use it. |
| El proveedor | Inside the island, around the Alea components. Each island is its own React tree, so every island that needs the theme has its own ThemeProvider; the choice is saved and a new island reads it when it loads. |
| Necesita client:load | The island needs a client directive to become interactive; client:load does it as soon as the page loads. The server still renders its HTML first. |
| Renderizado en servidor | Print themeScript() in the head of the page with is:inline and set:html, so the saved theme is on the page before the first paint. |
// src/components/Hello.tsximport '@alea-design/ui/styles.css';import { ThemeProvider, Button } from '@alea-design/ui';export default function Hello() {return (<ThemeProvider defaultTheme="system"><Button>Hello</Button></ThemeProvider>);}
<!-- src/pages/index.astro -->---import Hello from '../components/Hello';import { themeScript } from '@alea-design/ui';---<html lang="en"><head><script is:inline set:html={themeScript()} /></head><body><Hello client:load /></body></html>
Un proyecto que funciona con esta configuración: examples/astro.
TanStack Start
Rutas por archivos con renderizado en el servidor por defecto. Los componentes son React normal: sin directivas. Start necesita Vite 8 o posterior, y su plugin va antes del plugin de React.
Qué | Dónde y cómo |
|---|---|
| La hoja de estilos | Import it in the root route, src/routes/__root.tsx. |
| El proveedor | In the root component, around the Outlet. Passing navigate and useHref (from the router history) makes Alea links move through the router without reloading the page. |
| Código de cliente | All of it: nothing needs a directive. |
| Renderizado en servidor | Put ThemeScript next to HeadContent in the head of the document component, with suppressHydrationWarning on the html element. |
// vite.config.tsimport { defineConfig } from 'vite';import { tanstackStart } from '@tanstack/react-start/plugin/vite';import viteReact from '@vitejs/plugin-react';export default defineConfig({ plugins: [tanstackStart(), viteReact()] });
// src/routes/__root.tsximport '@alea-design/ui/styles.css';import { HeadContent, Outlet, Scripts, createRootRoute, useRouter } from '@tanstack/react-router';import { ThemeProvider, ThemeScript } from '@alea-design/ui';export const Route = createRootRoute({ component: Root });function Root() {const router = useRouter();return (<html lang="en" suppressHydrationWarning><head><HeadContent /><ThemeScript /></head><body><ThemeProvider defaultTheme="system" navigate={(to) => router.history.push(to)} useHref={(href) => router.history.createHref(href)}><Outlet /></ThemeProvider><Scripts /></body></html>);}
Un proyecto que funciona con esta configuración: examples/tanstack-start.
Renderizado en el servidor, en cada configuración
Qué | Cómo funciona |
|---|---|
| El tema | El servidor renderiza el tema que pongas en defaultTheme (oscuro si no pones ninguno), porque no puede conocer la elección guardada. ThemeScript se ejecuta antes del primer pintado y pone la elección guardada, o la del sistema, en el elemento html. Tras la hidratación, ThemeProvider lee la elección guardada y vuelve a renderizar con ella, sin aviso de hidratación; suppressHydrationWarning en el elemento html cubre el atributo que puso el script. |
| La ventana | Alea nunca lee window, document ni localStorage mientras renderiza: usa efectos y suscripciones que solo se ejecutan en el navegador. Haz lo mismo en tus propios componentes. |
| Iconos por nombre | Un Icon con name="search" carga su glifo la primera vez que se usa y hasta entonces dibuja una caja vacía del mismo tamaño. No necesita un límite de Suspense. El servidor renderiza esa caja también, salvo que esperes preloadIcons(['search']) antes de renderizar. Un icono dado como componente (icon={Search}) se renderiza al instante en todas partes. |
import { Icon, preloadIcons } from '@alea-design/ui';await preloadIcons(['search']); // before rendering on the server<Icon name="search" />