Saltar al contenido
Alea
Pro
  1. Alea
  2. Desarrollo
  3. Guías por framework

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 estilosImport it once in the entry file, src/main.tsx.
El proveedorWrap 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 clienteAll of it: nothing needs a directive.
Renderizado en servidorNone, so ThemeScript is not needed.
// src/main.tsx
import '@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 estilosImport it in the root layout, app/layout.tsx.
El proveedorA 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 servidorPut 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.tsx
import '@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 estilosImport it in pages/_app.tsx: Next.js allows global CSS from a package there.
El proveedorWrap the page component in ThemeProvider in pages/_app.tsx.
Código de clienteAll of it: nothing needs a directive.
Renderizado en servidorPut 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.tsx
import '@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.tsx
import { 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 estilosImport it in app/root.tsx.
El proveedorIn 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 clienteAll of it: nothing needs a directive.
Renderizado en servidorPut ThemeScript in the head of the Layout export of the same file, with suppressHydrationWarning on the html element.
// app/root.tsx
import '@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 estilosImport it in the island component: Astro adds the CSS to the pages that use it.
El proveedorInside 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:loadThe 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 servidorPrint 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.tsx
import '@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 estilosImport it in the root route, src/routes/__root.tsx.
El proveedorIn 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 clienteAll of it: nothing needs a directive.
Renderizado en servidorPut ThemeScript next to HeadContent in the head of the document component, with suppressHydrationWarning on the html element.
// vite.config.ts
import { 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.tsx
import '@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 temaEl 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 ventanaAlea 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 nombreUn 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" />