@nizivo/react
v0.5.1
Published
Nizivo React components: server-first, accessible, themed by @nizivo/tokens
Readme
@nizivo/react
Accessible React components for the Nizivo design system. Server Components by default: only the interactive leaves are 'use client', so a page that renders a Button, a Card or a Field with a native control ships no Nizivo JavaScript. The styling is plain CSS that reads the tokens of @nizivo/tokens.
Pre-release. 0.x versions go to the next dist-tag, and a minor release may break (with a BREAKING note in the changelog).
Install
pnpm add @nizivo/react@next @nizivo/tokens@next- Peers:
reactandreact-dom^19.2.0(@types/react^19.2.0is optional). - ESM only, no CommonJS build. Node
>=22.12can stillrequire()it. - Install
@nizivo/tokensas a direct dependency too: your CSS imports itstokens.css. The two always release at the same version. - Browser support floor: Chrome and Edge 123, Firefox 129, Safari and iOS 17.5.
Import the CSS once
The JavaScript never imports CSS. In your root layout (or global stylesheet), import, in this order:
@nizivo/tokens/tokens.css, the only source of token CSS;@nizivo/react/css/base.css, the shared accessibility floor (the focus ring and forced-colours rules of every control);- one
@nizivo/react/css/<name>.cssper component you use.
// app/layout.tsx
import '@nizivo/tokens/tokens.css';
import '@nizivo/react/css/base.css';
import '@nizivo/react/css/button.css';
import '@nizivo/react/css/field.css';
import '@nizivo/react/css/input.css';Every stylesheet starts with the same @layer statement, so the order a bundler emits them in never changes which rule wins.
Or import a single file, @nizivo/react/styles.css: the tokens, base.css and every component's stylesheet. Import either it or the files above, never both. It already holds a byte-identical copy of @nizivo/tokens/tokens.css from the same release, so the two never drift apart.
// app/layout.tsx
import '@nizivo/react/styles.css';With Tailwind CSS v4, import either route inside layer(components) and add @nizivo/tokens/tailwind.css.
Keep light-dark() intact, or nested theme scopes and inverse surfaces stop following their own theme:
- Next.js with Turbopack: set your browser targets to the support floor (a
browserslistofchrome >= 123,edge >= 123,firefox >= 129,safari >= 17.5,ios_saf >= 17.5). - Vite 8: set
build.cssTargetto['chrome123', 'edge123', 'firefox129', 'safari17.5', 'ios17.5']. Abrowserslistis not enough; withoutcssTargeta plain build lowers everylight-dark()value. - Astro 7.3: nothing to set.
Use
import { Button } from '@nizivo/react/button';
import { Field } from '@nizivo/react/field';
import { Input } from '@nizivo/react/input';
export default function NewProduct() {
return (
<form>
<Field label="Product title" description="Shown on product pages and checkout." required>
<Input name="title" placeholder="e.g. Stoneware mug" />
</Field>
<Button variant="primary" type="submit">
Save
</Button>
</form>
);
}Each component has its own subpath (@nizivo/react/<name>, kebab-case) and its own stylesheet (@nizivo/react/css/<name>.css). The root @nizivo/react barrel exports the same components.
Compound components are namespaces, and the interactive ones (Select, Dialog, Tooltip, Menu, Tabs) run on React Aria. A Server Component can render them as they are:
import { Select } from '@nizivo/react/select';
<Select.Root defaultValue="30d" name="period" aria-label="Period">
<Select.Trigger />
<Select.Popup>
<Select.Item value="7d">Last 7 days</Select.Item>
<Select.Item value="30d">Last 30 days</Select.Item>
</Select.Popup>
</Select.Root>;Theme
Light by default. Set data-nz-theme on <html> to system (follows the operating system, no script, no flash), light or dark. Any element with data-nz-theme starts a nested scope.
To remember a reader's choice, render ThemeScript in <head> and put suppressHydrationWarning on <html>. Before first paint it reads localStorage key nz-theme (light, dark or system) and sets data-nz-theme on <html>; with nothing stored, or storage blocked, it leaves your default alone and never throws. It also sets data-nz-platform, so Kbd shows ⌘ or Ctrl. Your theme switch stores the choice under the same key and sets the attribute.
import { ThemeScript } from '@nizivo/react/theme-script';
<html lang="en" data-nz-theme="system" suppressHydrationWarning>
<head>
<ThemeScript nonce={nonce} />
</head>
<body>{children}</body>
</html>;Under a strict CSP, pass the nonce as above or allow the script by hash: 'sha256-nA1HUQWDdDUFqqoxWbjzCzcYSjzWLEe0pwDdRCMGqOw='. The plain string is exported as themeScript for Astro (<script is:inline set:html={themeScript} />) and Vite (transformIndexHtml).
Components in 0.1
Text, Heading, Stack, Inline, Grid, Container, Separator; Button, IconButton; Badge, Card, Delta, ProgressBar, ProgressRing, Meter, Skeleton, EmptyState, InlineError, Alert, Kbd, Image; Field, Fieldset, Input, Textarea, Checkbox, RadioGroup, Switch, SegmentedControl, Select, NativeSelect; Tooltip, Dialog, Menu, Tabs, TabNav; ThemeScript and NizivoConfig.
Documentation
The docs site is coming. Until then:
- Getting started for Next.js, Vite and Astro, and the Tailwind recipe (the docs site's source).
- The plan: §5 (packages, exports and the CSS import rule), §11 (framework setup), §12 (overlays), §16.4 (RTL).
Licence
Apache-2.0. Copyright 2026 Neev Technologies. The icon drawings inlined into some components come from Lucide (ISC): see THIRD_PARTY_NOTICES.
