Skip to content
Nizivo UI

Astro

Use Nizivo in Astro 7 with @astrojs/react.

Everything on this page is checked by the Astro fixture (fixtures/astro, Astro 7.3.8 with @astrojs/react 7.0.1, static output, astro build and astro preview) on the packed packages, except where a note says otherwise.

Install

pnpm add @nizivo/react@next @nizivo/tokens@next

Add React to Astro with @astrojs/react. @nizivo/react needs react and react-dom ^19.2.0.

Import the CSS once

In your base layout, import the tokens, the accessibility floor and one stylesheet per component you use. Keep the .css extension. Astro turns the imports into one render-blocking <link> on every page, server-only pages included, and keeps every light-dark() value with no extra setting.

src/layouts/Base.astro
---
import '@nizivo/tokens/tokens.css';
import '@nizivo/react/css/base.css';
import '@nizivo/react/css/card.css';
import { themeScript } from '@nizivo/react/theme-script';
---
<html lang="en" data-nz-theme="system">
  <head>
    <script is:inline set:html={themeScript} />
  </head>
  <body><slot /></body>
</html>

Theme

data-nz-theme="system" follows the operating system with no script and no flash; light or dark forces one theme. themeScript, inline in <head>, applies a stored choice before first paint: it reads localStorage key nz-theme (light, dark or system) and sets data-nz-theme on <html>. It also sets data-nz-platform so Kbd shows ⌘ or Ctrl from the first frame. Your theme switch saves the choice with localStorage.setItem('nz-theme', theme) and sets the attribute itself; see Next.js for the details and the CSP hash. The fixture checks every frame from first paint for the OS preference, a stored choice and a static attribute, and catches a theme set late as a flash.

Instead of the three stylesheets above, you can import @nizivo/react/styles.css alone: the tokens, base.css and every component. Import either it or the separate files, never both. The fixture checks the separate files.

Islands

  • A component rendered without a client:* directive is static HTML and ships no JavaScript: the fixture's server-only pages load no script at all, barrel imports included.
  • Children passed from an .astro file arrive as static HTML, outside their Root's context. Compose a compound component (a Select.Root with its parts) inside one .tsx island. With the parts as .astro children the build fails with React Aria's "ListBoxItem cannot be rendered outside a collection" (seen once on a throwaway page; the fixture does not keep a failing build).
  • Each island gets its own useId prefix, so two islands on one page never repeat an id.
  • client:load and client:visible hydrate with no console message.
  • Context does not cross islands: an island that needs a non-default locale wraps its own NizivoConfig. This follows from how Astro hydrates islands; the fixture does not exercise it.
  • Props to a client:* island must be serialisable, so no functions. This is Astro's rule; the fixture does not exercise it.

Running Astro under a coding agent

Astro 7 detects coding agents and then runs astro dev and astro preview in the background with a lock file. Pass --ignore-lock to keep them in the foreground. astro build behaves the same either way. Set ASTRO_TELEMETRY_DISABLED=1 to stop Astro's telemetry.

Not covered by the fixture yet

output: 'server' (it needs an adapter), astro dev, client:idle and client:only are not checked yet. The fixture runs in Chromium.

On this page