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, andonOpenChange - 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 wrapsI18nProviderwitharso placement still flips. That workaround does not sethtml lang="ar".direction="ltr"(explicit) with no locale wrapsI18nProviderwithen-USso a nested LTR island does not inherit a parent RTL locale for overlay placement.locale="en-US"plusdirection="rtl"keeps English formatting; overlays may not flip.- If you omit
directionbut passlocale,diris derived from RACisRTL(locale). useDirection()returns{ direction, isRtl }and defaults toltrwhen 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.