# Primitives and themes

> Choose the native control API and load its CSS. Keep all theme state aligned.

`@n3wth/ui/primitives` re-exports the core Astryx API and explicitly exports `ToastViewport` and its props. The UI package pins the Astryx dependency; applications in this workspace must consume it through UI rather than importing Astryx directly.

## Use the correct Button API

The native button receives its text through `label`:

```tsx
import { Button } from '@n3wth/ui/primitives'

export function RefreshButton() {
  return <Button label="Reload page" onClick={() => window.location.reload()} />
}
```

The root compatibility button instead requires `children` and retains older props such as `variant="glass"`, `leftIcon`, and responsive `size` objects. These are different APIs even though both exports are named `Button`. Do not move imports between entry points without checking their types.

The native `label` and `onClick` example matches the packaged starter. For additional controls and props, use the declarations supplied by your installed version; the primitives surface follows the package's pinned Astryx version, currently `0.1.6` in the 2.0.0 source.

## Choose a CSS setup

| Application | Setup |
| --- | --- |
| Site components and native primitives | Import `@n3wth/ui/site.css` once |
| App uses Tailwind v4 utilities | Import Tailwind, the token bridge, and site CSS in the global stylesheet |
| App retains root compatibility adapters | Use `@n3wth/ui/styles` and scan the built package for Tailwind classes |

For a consumer whose global stylesheet is `src/app.css` and dependencies are in the project root:

```css
@import 'tailwindcss';
@import '@n3wth/ui/tailwind-theme.css';
@import '@n3wth/ui/site.css';
```

This assumes Tailwind v4 is already configured in the application. The token bridge contains Tailwind directives and belongs in that pipeline, not an unprocessed browser stylesheet.

When retaining compatibility components, replace the final import and add an explicit source:

```css
@import 'tailwindcss';
@import '@n3wth/ui/tailwind-theme.css';
@import '@n3wth/ui/styles';
@source '../node_modules/@n3wth/ui/dist/**/*.js';
```

Resolve `@source` relative to the CSS file. Workspace consumers scan their built `packages/ui/dist` directory instead. The current UI docs app uses this compatibility setup because its examples still render root adapters. `styles` already includes the site foundation, so it is not necessary to import both stylesheets.

## Set the theme mode

`N3wthProvider` accepts `children` and optional `mode`. The default is `dark`; pass `light` or `system` to select another mode. The provider alone does not provide the local-storage preference behavior of `useTheme`.

For an interactive app, keep the provider and toggle on the same state:

```tsx
'use client'

import type { ReactNode } from 'react'
import { useTheme } from '@n3wth/ui'
import { N3wthProvider } from '@n3wth/ui/site'
import { Button } from '@n3wth/ui/primitives'

export function ThemeRoot({ children }: { children: ReactNode }) {
  const { theme, toggleTheme } = useTheme()

  return (
    <N3wthProvider mode={theme}>
      <Button
        label={theme === 'dark' ? 'Use light theme' : 'Use dark theme'}
        onClick={toggleTheme}
      />
      {children}
    </N3wthProvider>
  )
}
```

Import the CSS in the app's global entry as described above.

| `useTheme` option | Default | Meaning |
| --- | --- | --- |
| `defaultTheme` | `dark` | Server-rendered value and fallback when there is no saved value or light-system preference |
| `storageKey` | `n3wth-theme` | Local-storage preference key |
| `attribute` | `data-theme` | Attribute written on the document root |

The hook returns `theme`, `setTheme`, `toggleTheme`, `isDark`, and `isLight`. It accepts stored `dark` or `light` values, then checks the system's light preference, then uses the default. Storage access failures are caught.

Calls that use the same storage key share theme updates. Automatic system changes do not save a preference. The hook follows system changes until the user selects a theme with `setTheme` or `toggleTheme`. Use provider `mode="system"` when you do not need a saved user preference.

For server-rendered apps, the hook's server default can differ from the saved browser preference. The UI docs app uses a blocking theme initialization script to set document attributes before paint. Such a script helps initial colors; it does not by itself make theme-dependent server markup identical to the client.

## Typography and tokens

The canonical theme source currently selects Suisse Intl for headings and body, and Geist Mono for code. The Tailwind bridge maps `font-display`, `font-sans`, and `font-mono` to the corresponding theme font-family variables. Older instructions naming Satoshi headings do not describe this theme revision.

Suisse Intl files are not distributed on npm. The release package omits their font rules and uses the theme's system-font fallback. See [font troubleshooting](/ui/troubleshooting#font-resolution-fails) for older artifacts and workspace differences.

## Sources

- [Native exports](https://github.com/n3wth/n3wth/blob/main/packages/ui/src/primitives/index.ts)
- [Root Button adapter](https://github.com/n3wth/n3wth/blob/main/packages/ui/src/atoms/Button/Button.tsx)
- [Provider](https://github.com/n3wth/n3wth/blob/main/packages/ui/src/theme/N3wthProvider.tsx) and [theme definition](https://github.com/n3wth/n3wth/blob/main/packages/ui/src/theme/n3wthTheme.ts)
- [Theme hook](https://github.com/n3wth/n3wth/blob/main/packages/ui/src/hooks/useTheme.ts) and [initial-paint script](https://github.com/n3wth/n3wth/blob/main/apps/ui-docs/public/theme-init.js)
- [CSS generation](https://github.com/n3wth/n3wth/blob/main/packages/ui/scripts/build-styles.mjs) and [current app stylesheet](https://github.com/n3wth/n3wth/blob/main/apps/ui-docs/demo/demo.css)
