Files
2026-09-04 09:04:51 -07:00

649 lines
17 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Upgrading from v1 to v2
description: A comprehensive guide for migrating your Gui project from v1 to v2
---
Gui v2 aligns with modern web standards, improves performance, and simplifies APIs. The migration is moderate -- mostly removing deprecated APIs, renaming props, and updating your config. This guide walks through every change.
## Prerequisites
Gui 2 requires:
- **React 19+**
- **React Native 0.81+** (with New Architecture support)
- **TypeScript 5+**
Upgrade these first before proceeding.
## 1. Update Dependencies
Bump all `@hanzogui/*` and `hanzogui` packages to `^2.0.0`:
```bash
# if using npm/yarn/pnpm, update all hanzogui packages
npx hanzogui check
```
### Removed packages
- `@hanzogui/animations-moti` -- use `@hanzogui/animations-reanimated` (same API)
- `@hanzogui/image-next` -- use `Image` from `hanzogui` directly
### Add resolutions
In monorepos, you may want to add resolutions to avoid duplicate copies of core packages (critical for context/provider to work):
```json
"resolutions": {
"@hanzogui/core": "^2.0.0",
"@hanzogui/web": "^2.0.0",
"@hanzo/gui": "^2.0.0"
}
```
## 2. Config Migration (v4 to v5)
You don't have to migrate to Config v5, and it changes some default style behaviors that may
cause some issues. It's easier to migrate to Gui 2 first, then Config v5 after if you do choose to.
Config v5 is mostly a superset of v5, but some media query names changed and some themes and colors are different.
### Update imports
```tsx
// before
import { defaultConfig } from '@hanzogui/config/v4'
// after
import { defaultConfig } from '@hanzogui/config/v5'
import { animations } from '@hanzogui/config/v5-css' // animations are now separate
```
Animations are no longer bundled with the config. Choose your driver:
- `@hanzogui/config/v5-css` -- CSS transitions (smallest bundle, web-only)
- `@hanzogui/config/v5-rn` -- React Native Animated API
- `@hanzogui/config/v5-reanimated` -- Reanimated (best native performance)
- `@hanzogui/config/v5-motion` -- Motion (Web Animations API, experimental)
### Add animations to config
```tsx
import { defaultConfig } from '@hanzogui/config/v5'
import { animations } from '@hanzogui/config/v5-css'
import { createGui } from '@hanzo/gui'
export const config = createGui({
...defaultConfig,
animations,
})
export type Conf = typeof config
declare module 'hanzogui' {
interface GuiCustomConfig extends Conf {}
}
```
### Settings changes
All root-level `createGui` settings have moved into the `settings` object:
```tsx
// before (v1)
createGui({
defaultFont: 'body',
disableRootThemeClass: true,
// ...
})
// after (v2)
createGui({
...defaultConfig,
settings: {
...defaultConfig.settings,
// your overrides
},
})
```
**Removed settings:**
- `maxDarkLightNesting` -- removed entirely
- `cssStyleSeparator` -- removed entirely
- `themeClassNameOnRoot` -- handled by `addThemeClassName`
- `disableRootThemeClass` -- now part of `settings`
### Flex and position defaults
v5 changes two important defaults:
- `flexBasis` changes from `auto` to `0` (React Native standard)
- `position` changes from `relative` to `static` (browser default)
If your layout relies on the old behavior, restore it:
```tsx
settings: {
...defaultConfig.settings,
styleCompat: 'legacy', // restores flexBasis: auto
defaultPosition: 'relative', // restores position: relative
}
```
Otherwise, you may need to add `position="relative"` explicitly on containers that have absolutely-positioned children, and add `flexBasis="auto"` where needed.
### Media query renames
- `$2xl` → `$xxl`
- `$2xs` → `$xxs`
- `$max2Xl` → `$max-xxl`
- `$maxXl` → `$max-xl`
- `$maxLg` → `$max-lg`
- `$maxMd` → `$max-md`
- `$maxSm` → `$max-sm`
Max queries changed from camelCase to kebab-case. New height-based queries are also available (`$height-sm`, `$height-md`, etc.) and a `$pointerTouch` query.
Breakpoint values now match Tailwind CSS: 640, 768, 1024, 1280, 1536.
### Colors and themes
Colors have been updated to Radix Colors v3 with slightly different values. Legacy colors are available at `@hanzogui/colors/legacy`.
v5 adds new color themes: `orange`, `pink`, `purple`, `teal`, `gray`, `neutral`.
```tsx
// before (v1) - manual theme-builder
import { createThemes, defaultComponentThemes } from '@hanzogui/theme-builder'
const themes = createThemes({ ... })
// after (v2) - simplified
import { createV5Theme, defaultChildrenThemes } from '@hanzogui/themes/v5'
const themes = createV5Theme({
childrenThemes: {
...defaultChildrenThemes,
// add custom color themes
cyan: { light: cyan, dark: cyanDark },
},
})
```
Component themes are off by default in v5. Use `defaultProps` in your config instead:
```tsx
createGui({
...defaultConfig,
defaultProps: {
Button: { theme: 'accent' },
},
})
```
### Theme tree-shaking (SSR optimization)
On the client, you can skip loading theme JS and hydrate from CSS variables instead:
```tsx
themes: process.env.VITE_ENVIRONMENT === 'client'
? ({} as typeof themes)
: themes,
```
<Notice theme="blue">
Once your v5 config is set up, run `npx hanzogui generate-prompt` and commit the output
to your repo. This helps AI assistants understand your Gui config and can speed up the
rest of the migration.
</Notice>
## 3. Prop Renames
These are the most common find-and-replace changes.
### `animation` to `transition`
```tsx
// before
<View animation="bouncy" />
<Sheet.Overlay animation="lazy" />
// after
<View transition="bouncy" />
<Sheet.Overlay transition="lazy" />
```
The TypeScript type also changed: `AnimationProp` to `TransitionProp`.
### `tag` to `render`
```tsx
// before
<View tag="nav" />
<View tag="button" />
// after
<View render="nav" />
<View render="button" />
```
For `tag="a"`, consider using the `Anchor` component instead.
### `Stack` to `View`
```tsx
// before
import { Stack, type StackProps } from '@hanzo/gui'
// after
import { View, type ViewProps } from '@hanzo/gui'
```
### `themeInverse` / `<Theme inverse>` to `theme="accent"`
```tsx
// before
<Button themeInverse>Primary</Button>
<Theme inverse><Card>...</Card></Theme>
// after
<Button theme="accent">Primary</Button>
<Theme name="accent"><Card>...</Card></Theme>
```
### `space` / `spaceDirection` to `gap`
```tsx
// before
<YStack space="$4" spaceDirection="both">
// after
<YStack gap="$4">
```
### `onHoverIn` / `onHoverOut`
```tsx
// before
<View onHoverIn={handler} onHoverOut={handler} />
// after
<View onPointerEnter={handler} onPointerLeave={handler} />
```
### `ellipse` to `numberOfLines`
```tsx
// before
<Text ellipse>Long text...</Text>
// after
<Text numberOfLines={1}>Long text...</Text>
```
## 4. Shadow Migration
Replace React Native shadow props with CSS `boxShadow`:
```tsx
// before
<View
shadowColor="$shadow3"
shadowRadius={20}
shadowOffset={{ height: 10, width: 0 }}
/>
// after
<View boxShadow="0 10px 20px $shadow3" />
```
The format is `x y blur color`. Multiple shadows are comma-separated. Spread and inset are also supported.
## 5. Accessibility Props
All React Native accessibility props are replaced with web-standard ARIA equivalents:
- `accessibilityLabel` → `aria-label`
- `accessibilityRole` → `role`
- `accessibilityHint` → `aria-describedby`
- `accessibilityState={{ disabled }}` → `aria-disabled`
- `accessibilityState={{ selected }}` → `aria-selected`
- `accessibilityState={{ checked }}` → `aria-checked`
- `accessibilityState={{ busy }}` → `aria-busy`
- `accessibilityState={{ expanded }}` → `aria-expanded`
- `accessibilityValue` → `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext`
- `accessibilityElementsHidden` → `aria-hidden`
- `accessibilityViewIsModal` → `aria-modal`
- `accessibilityLiveRegion` → `aria-live`
- `accessible` → `tabIndex={0}`
- `focusable` → `tabIndex`
- `nativeID` → `id`
## 6. Component API Changes
### Input
Input now uses web-standard HTML attributes as the primary API:
```tsx
// before
<Input
keyboardType="email-address"
secureTextEntry
returnKeyType="send"
textContentType="emailAddress"
onChangeText={(text) => setText(text)}
onKeyPress={(e) => {
if (e.nativeEvent.key === 'Enter') submit()
}}
editable={false}
/>
// after
<Input
inputMode="email"
type="password"
enterKeyHint="send"
autoComplete="email"
onChange={(e) => setText(e.target?.value ?? e.nativeEvent?.text ?? '')}
onKeyDown={(e) => {
if (e.key === 'Enter') submit()
}}
readOnly
/>
```
The old React Native props still work but are deprecated.
### Image
Image now prefers web-standard `src` for URLs and native `require()` results.
React Native's `source` remains supported during migration, but is deprecated:
```tsx
// before
<Image
source={{ uri: 'https://example.com/photo.jpg', width: 200, height: 200 }}
resizeMode="cover"
/>
// after
<Image
src="https://example.com/photo.jpg"
width={200}
height={200}
objectFit="cover"
/>
```
The `width` and `height` props control layout. Percentage dimensions also work
when the parent has defined dimensions.
### Button
- Text style props (`fontFamily`, `fontSize`, etc.) removed from the direct API -- style text through child components
- Now defaults to `type="button"` to prevent accidental form submissions
- `useButton` hook is deprecated
### ListItem
- Text style props removed -- use `ListItem.Text` and `ListItem.Subtitle` child components
- Internal spacing props (`spaceFlex`, `scaleSpace`) removed
### Tabs
- `activationMode` now defaults to `'manual'` (was `'automatic'`) -- users must click or press Enter to change tabs, matching web standards
- `Tabs.Trigger` deprecated in favor of `Tabs.Tab`
### Group
- `Group.Item` wrapper is now required (no more auto-cloning direct children)
- Removed props: `space`, `separator`, `scrollable`, `showScrollIndicator`, `disablePassBorderRadius`, `forceUseItem`
- Add `<Separator />` manually between items when needed
### Sheet / Popover.Sheet
`Popover.Sheet` sub-components are replaced with standalone `Sheet`:
```tsx
// before
<Adapt when="maxMd" platform="touch">
<Popover.Sheet modal dismissOnSnapToBottom>
<Popover.Sheet.Frame p="$4">
<Adapt.Contents />
</Popover.Sheet.Frame>
<Popover.Sheet.Overlay animation="quick" />
</Popover.Sheet>
</Adapt>
// after
<Adapt when="max-md" platform="touch">
<Sheet modal dismissOnSnapToBottom>
<Sheet.Frame p="$4">
<Adapt.Contents />
</Sheet.Frame>
<Sheet.Overlay transition="quick" />
</Sheet>
</Adapt>
```
### Select
`SelectLabel` is now SizableText-based instead of ListItem-based.
## 7. Removed APIs
- `<Spacer />` removed from core -- import from `@hanzogui/spacer`
- `composeEventHandlers` -- compose manually: `(val) => { a(val); b?.(val) }`
- `useTheme(props)` -- use `<Theme>` component instead
- `ThemeableStack` -- use `View`
- `backgrounded` prop -- use `bg="$background"`
- `selectable` prop -- use `select="text"`
- `animatePresence` prop (inline) -- wrap with `<AnimatePresence>` component
- `scrollbarWidth` prop -- use CSS
- `isWindowDefined` -- use `isBrowser` from `@hanzogui/constants`
- `useTheme` from `@hanzogui/next-theme` -- renamed to `useThemeSetting`
- `@hanzogui/react-native-use-responder-events` -- use pointer events (`onPointerDown`, etc.)
## 8. Native Setup
v2 requires explicit setup imports for native features. Add these at the top of your app entry before any Gui imports. Note these are optional and mostly new, so only if you were using native gradient, or toast before do you need to do this.
```tsx
// portals (Sheet, Dialog, Popover, Select, Toast)
import '@hanzogui/native/setup-teleport'
// LinearGradient
import '@hanzogui/native/setup-expo-linear-gradient'
// Toast (burnt)
import '@hanzogui/native/setup-burnt'
// Menu (zeego)
import '@hanzogui/native/setup-zeego'
// for smoother Sheet on native:
import '@hanzogui/native/setup-gesture-handler'
```
### Expo Router entry point
With Expo Router, these setup imports must run **before** `expo-router/entry`. Create a custom entry point:
```js
// index.js (at project root)
import '@hanzogui/native/setup-zeego'
// add other setup imports here as needed
import 'expo-router/entry'
```
Then update your `package.json`:
```json
{
"main": "index.js"
}
```
This ensures native modules like zeego are configured before Expo Router initializes your app.
## 9. Build Config
Not much has changed, but there's some nice improvements you can make:
### Vite
```tsx
import { hanzoguiAliases, hanzoguiPlugin } from '@hanzogui/vite-plugin'
export default {
plugins: [hanzoguiPlugin()],
resolve: {
alias: [
...hanzoguiAliases({
rnwLite: true, // use lightweight react-native-web
svg: true,
}),
],
},
}
```
Create a `hanzogui.build.ts` at your project root:
```tsx
import type { GuiBuildOptions } from '@hanzo/gui'
export default {
components: ['@hanzo/gui'],
config: './src/hanzogui.config.ts',
outputCSS: './src/hanzogui.generated.css',
} satisfies GuiBuildOptions
```
### Metro (Expo / React Native)
No special Metro configuration is needed for Gui v2. A standard Expo Metro config works out of the box:
```js
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
module.exports = config
```
### Next.js (Turbopack)
Add `resolveExtensions` and a `react-native-safe-area-context` shim:
```js
// next.config.js
module.exports = {
turbopack: {
resolveAlias: {
'react-native': 'react-native-web',
'react-native-svg': '@hanzogui/react-native-svg',
'react-native-safe-area-context': './shims/react-native-safe-area-context.js',
},
resolveExtensions: [
'.web.tsx',
'.web.ts',
'.web.js',
'.web.jsx',
'.tsx',
'.ts',
'.js',
'.jsx',
'.json',
],
},
}
```
### CSS
We recommend generating Gui CSS to `hanzogui.generated.css`, just so it's easier to configure linters and such:
```tsx
// before
import './hanzogui.css'
// after
import './hanzogui.generated.css'
```
Generate it with: `npx hanzogui generate-css --output ./src/hanzogui.generated.css`
## 10. Quick Reference: Find-and-Replace Patterns
These are the most common replacements you can do across your codebase. Run carefully and review each change:
```
animation= -> transition=
AnimationProp -> TransitionProp
AnimationKeys -> TransitionKeys
tag= -> render=
themeInverse -> theme="accent"
<Theme inverse -> <Theme name="accent"
<Stack -> <View
</Stack> -> </View>
StackProps -> ViewProps
onHoverIn= -> onPointerEnter=
onHoverOut= -> onPointerLeave=
$2xl= -> $xxl=
$2xs= -> $xxs=
maxMD -> max-md
maxLG -> max-lg
maxSM -> max-sm
maxXL -> max-xl
max2Xl -> max-xxl
```
## 11. New Features Worth Knowing
While not required for migration, these v2 features can improve your app:
- **`boxShadow`** -- full CSS box-shadow support with tokens
- **`backgroundImage`** -- CSS gradients with token support
- **`filter`**, **`mixBlendMode`** -- graphical effects
- **`scope` prop** on Dialog, Popover, Sheet, Tooltip -- mount once at root for performance
- **`activeStyle` / `activeTheme`** on Switch, Checkbox, ToggleGroup, Tabs
- **Multiple animation drivers** via `animatedBy` prop
- **`transition` delay and enter/exit control** -- `transition={{ enter: 'lazy', exit: 'quick' }}`
- **Menu and ContextMenu** -- new components with native platform rendering
- **Headless components** -- `@hanzogui/switch-headless`, `@hanzogui/checkbox-headless`, etc.
- **CLI commands** -- `hanzogui build`, `hanzogui generate`, `hanzogui generate-prompt`, `hanzogui check`
- **~32% smaller core bundle** (37KB to 25KB gzipped)
## Migration Checklist
- [ ] Upgrade React 19+, React Native 0.81+, TypeScript 5+
- [ ] Bump all `@hanzogui/*` packages to `^2.0.0`
- [ ] Add package resolutions for `@hanzogui/core`, `@hanzogui/web`, `hanzogui`
- [ ] Update config from `@hanzogui/config/v4` to `@hanzogui/config/v5`
- [ ] Import animations separately (`@hanzogui/config/v5-css` or other driver)
- [ ] Add `declare module 'hanzogui'` type augmentation
- [ ] Move root-level settings into `settings` object
- [ ] Rename `animation` prop to `transition` everywhere
- [ ] Rename `tag` prop to `render`
- [ ] Replace `Stack` with `View`, `StackProps` with `ViewProps`
- [ ] Replace `themeInverse` / `<Theme inverse>` with `theme="accent"`
- [ ] Replace `space` / `spaceDirection` with `gap`
- [ ] Replace `onHoverIn` / `onHoverOut` with pointer events
- [ ] Migrate shadows to `boxShadow`
- [ ] Update media query names (`$2xl` to `$xxl`, max queries to kebab-case)
- [ ] Update accessibility props to ARIA equivalents
- [ ] Update Input props to web-standard API
- [ ] Update Image from `source` to `src`
- [ ] Wrap Group children in `Group.Item`
- [ ] Update Tabs (`activationMode` default changed, `Trigger` to `Tab`)
- [ ] Replace `Popover.Sheet.*` with standalone `Sheet.*`
- [ ] Update Toast from `ToastProvider` wrapper to `Toaster` sibling
- [ ] Import `Spacer` from `@hanzogui/spacer` if used
- [ ] Replace `@hanzogui/animations-moti` with `@hanzogui/animations-reanimated`
- [ ] Add native setup imports (portals, LinearGradient, Toast, Menu)
- [ ] Review flex/position defaults (add `position="relative"` where needed)
- [ ] Update build config (Metro package exports, Vite aliases, CSS generation)
- [ ] Update snapshot tests (shadow colors now use `color-mix()`)
- [ ] Run `npx hanzogui check` to verify dependency consistency