Skip to content

Also available as Markdown

Getting started

This guide is the happy path for a Vite + React 19 app: install from npm, import CSS, wrap providers, and render a Button.

Install

pnpm add @var-ui/react @var-ui/core @var-ui/icons react react-dom react-aria-components
npm install @var-ui/react @var-ui/core @var-ui/icons react react-dom react-aria-components

@var-ui/icons is optional. Without it, pass icons={{}} to IconProvider.

You do not add typestyles to the app. It arrives with @var-ui/core.

Import CSS

import '@var-ui/core/styles.css';

That file includes every recipe, base HTML styles, and the default theme. Custom createDesignTheme modules and unused-recipe shedding use extract instead — see Installation.

Providers

Mount DesignSystemProvider with applyToDocument, IconProvider, and LayerProvider at the root:

import { defaultIcons } from '@var-ui/icons';
import { DesignSystemProvider, IconProvider, LayerProvider } from '@var-ui/react';

<DesignSystemProvider applyToDocument>
  <IconProvider icons={defaultIcons}>
    <LayerProvider>{children}</LayerProvider>
  </IconProvider>
</DesignSystemProvider>;

Without @var-ui/icons, pass icons={{}} to IconProvider.

applyToDocument puts the theme class on html. Isolate stacking and cover the visual viewport:

html {
  isolation: isolate;
}

body {
  position: relative;
}

First component

import { Button } from '@var-ui/react';

<Button>Click me</Button>;

Astro without React

pnpm add @var-ui/astro @var-ui/core

Import the same stylesheet in a layout, then import Astro components:

---
import '@var-ui/core/styles.css';
import { Button } from '@var-ui/astro';
---

<Button>Click me</Button>

See examples/astro-app in the repo for a full workspace tree. That example still extracts CSS with the TypeStyles Vite plugin because it lives in this monorepo.

Next

  • Installation — package list, peers, and TypeStyles extract for custom themes
  • Overlay lifecycle — overlay data-* attributes, positioner CSS variables, and onOpenChange
  • Theming — tokens, themes, and createDesignTheme

Direction, portals, and stacking

Direction and locale

Pass direction and a real locale on DesignSystemProvider:

<DesignSystemProvider applyToDocument direction="rtl" locale="he-IL">
  {children}
</DesignSystemProvider>

applyToDocument syncs html dir (and lang when locale is set) after hydration / on mount. It always writes dir for the resolved direction — including default ltr. If SSR set <html dir="rtl"> and the client provider omits direction/locale, mount will rewrite dir to ltr. SSR apps that care about first paint should set <html dir> (and lang if needed) themselves and pass matching direction/locale to the provider.

React Aria overlays (Popover, Menu, start / end placement) take direction from locale, not from dir. I18nProvider only accepts locale.

  • Prefer a real RTL locale (he-IL, ar-EG) so dates, numbers, and overlays agree.
  • direction="rtl" with no locale wraps I18nProvider with ar so placement still flips. That workaround does not set html lang="ar".
  • direction="ltr" (explicit) with no locale wraps I18nProvider with en-US so a nested LTR island does not inherit a parent RTL locale for overlay placement.
  • locale="en-US" plus direction="rtl" keeps English formatting; overlays may not flip.
  • If you omit direction but pass locale, dir is derived from RAC isRTL(locale).
  • useDirection() returns { direction, isRtl } and defaults to ltr when no provider is mounted.
  • Directional icons opt in: <Icon name="chevronRight" data-mirror={isRtl || undefined} />.

Do not set dir on the whole docs site to try this — use a DirectionProvider island (see Hidden).

Portals and stacking

Wrap the application in an isolating root so portaled dialogs and popovers stack above page z-index:

<body>
  <div class="root">{children}</div>
</body>
.root {
  isolation: isolate;
}

When DesignSystemProvider uses applyToDocument, put isolation: isolate on html or body instead of an extra wrapper.

Themed portals

Token CSS variables cascade from the theme class. If an overlay portals to document.body and the theme class is on a subtree, pass portalContainer pointing at the themed node (or use applyToDocument).

iOS 26+ Safari backdrops

Dialog and drawer backdrops use position: absolute under @supports (-webkit-touch-callout: none). Add body { position: relative; } so a scrolled page still covers the visual viewport.

Overlay data-* attributes, positioner CSS variables, and onOpenChange details are documented in Overlay lifecycle.