Installation

Add Adv UI to a Next.js, Expo or React Native app.

Install

Install the core package. pnpm and npm 7+ install its peer dependencies (Tamagui and React Native) automatically. On web, add React Native Web yourself: it is what react-native is aliased to, and no package asks for it.

pnpm add @advui/core @advui/theme @advui/icons tamagui
pnpm add react-native-web   # web only (Next.js, Vite)

Add the packages for the components you need. Each component page says which package it is in:

pnpm add @advui/data     # tables, data grid, tree view, timeline, stats
pnpm add @advui/charts   # bar, line, pie, scatter, radar charts and more (installs @advui/data too)
pnpm add @advui/editor   # rich text and Markdown editors

Or let the CLI detect your framework and do it for you:

npx advui init

Create a config

One config drives every component. Pick a preset or pass your brand colors:

tamagui.config.ts
import { createUniversalConfig } from '@advui/theme'

export const config = createUniversalConfig({
  preset: 'indigo',          // or 'violet', 'emerald', 'rose', 'slate'…
  colors: { primary: '#6366f1' }, // optional brand override
  radius: 'md',              // 'none' | 'sm' | 'md' | 'lg' | 'xl'
  fontScale: 'default',      // 'compact' | 'default' | 'large'
})

export default config

Next.js

Alias React Native to React Native Web and transpile the packages:

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  // Add @advui/data, @advui/charts and @advui/editor when you use them.
  transpilePackages: ['@advui/core', '@advui/theme', '@advui/icons', '@advui/utils', 'react-native-web'],
  turbopack: {
    resolveAlias: { 'react-native': 'react-native-web' },
    resolveExtensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'],
  },
}

export default nextConfig

Wrap the app in a client provider. Tamagui injects its CSS during SSR through React 19 style hoisting — no extra wiring:

app/providers.tsx
'use client'

import { UniversalProvider } from '@advui/core'
import { config } from '../tamagui.config'

export function Providers({ children }: { children: React.ReactNode }) {
  return <UniversalProvider config={config}>{children}</UniversalProvider>
}

Expo

Expo SDK 52+ configures Metro for monorepos automatically. Wrap your root layout and pass the safe-area insets so toasts and sheets stay clear of the notch and home indicator:

app/_layout.tsx
import { UniversalProvider } from '@advui/core'
import { Stack } from 'expo-router'
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
import { config } from '../tamagui.config'

function Providers() {
  const insets = useSafeAreaInsets()
  return (
    <UniversalProvider config={config} insets={insets}>
      <Stack />
    </UniversalProvider>
  )
}

export default function RootLayout() {
  return (
    <SafeAreaProvider>
      <Providers />
    </SafeAreaProvider>
  )
}
Expo GoNo custom native code is required: icons use react-native-svg (bundled with Expo) and animations use React Native Animated.

Link, Breadcrumb, Sidebar, Navigation Menu, Menu and List Item take an href. On web it is a real <a>; pass render with your router's link for client-side routing. iOS and Android have no <a>, so register your router once, at start, and a path in href navigates there too. Absolute URLs (https:, mailto:) open with the system either way.

app/_layout.tsx
import { setRouter } from '@advui/core'
import { router } from 'expo-router'

setRouter(router)

A router that comes from a hook, such as React Navigation's useLinkTo, is registered from an effect: useEffect(() => setRouter({ push: linkTo }), [linkTo]). Once a router is registered, drop any onPress={() => router.push(href)} beside an href, or the link navigates twice.

File picking (native)

File Upload and File Dropzone use the browser's dialog on web. Core ships no native file picker, so on iOS and Android register your app's once, at start. With expo-document-picker (it works in Expo Go):

app/_layout.tsx
import { setFilePicker } from '@advui/core'
import * as DocumentPicker from 'expo-document-picker'

setFilePicker(async ({ multiple, accept }) => {
  // The picker filters by MIME type only. Extensions (".pdf") are checked
  // after picking, so a list with one opens the picker unfiltered: filtering
  // by the rest ("image/*,.pdf") would hide the PDFs.
  const rules = accept?.split(',').map((rule) => rule.trim()) ?? []
  const result = await DocumentPicker.getDocumentAsync({
    multiple,
    type: rules.length && rules.every((rule) => rule.includes('/')) ? rules : '*/*',
  })
  if (result.canceled) return null
  return result.assets.map((asset) => ({
    name: asset.name,
    size: asset.size,
    type: asset.mimeType,
    uri: asset.uri,
  }))
})

Use a component

import { Button, Card, Input } from '@advui/core'

export function ProfileCard() {
  return (
    <Card>
      <Card.Header>
        <Card.Title>Profile</Card.Title>
      </Card.Header>
      <Card.Content>
        <Input placeholder="Email" />
      </Card.Content>
      <Card.Footer>
        <Button>Save</Button>
      </Card.Footer>
    </Card>
  )
}

Color mode

UniversalProvider follows the system setting by default. Read or change it anywhere with useColorMode(), or control it with the colorMode prop.

Edit this page on GitHub