Tamagui Starter Free

repository·main·Indexed 43 days ago

https://github.com/tamagui/starter-free

A monorepo starter template combining Tamagui, Solito, Next.js, and Expo for high-performance, cross-platform (Web and Native) application development. It includes a shared UI kit, core application logic, and pre-configured providers for theme management and safe area handling.

Tokens
6K
Snippets
20
Records
24
Agent score
55%

What's inside Tamagui Starter Free

  1. Understand the monorepo folder layout

    main

    The project is organized into apps for platform-specific entry points and packages for shared logic and UI.

    Apps

    • apps/next: The Next.js web application.
    • apps/expo: The Expo native application.

    Shared Packages (packages/)

    • packages/ui (@my/ui): Your custom UI kit, optimized by Tamagui.
    • packages/app: The core application logic. Most files should be imported from here.
      • features/: Organize code by feature here (avoid using a screens folder).
      • provider/: Contains app-wide providers and web-specific no-ops.
  2. Start the web and native apps

    main

    This monorepo uses yarn for dependency management and script execution. Use the following commands to run the different platforms:

    Web (Next.js)

    • Local development: yarn web
    • Dev with optimizer enabled: yarn web:extract (Note: slower than standard dev mode)
    • Production build: yarn web:prod

    Native (Expo)

    • Local development: yarn native

    To verify the Tamagui compiler output, add // debug as a comment to the top of any file to see debug output.

    yarn web
    yarn native
  3. Add new Native dependencies

    main

    If you are installing a library that contains native code (e.g., react-native-reanimated), you must install it specifically in the apps/expo directory:

    cd apps/expo
    yarn add <package-name>
    cd ..
    yarn
    cd apps/expo
    yarn add react-native-reanimated
    cd ..
    yarn
  4. Add new Pure JS dependencies

    main

    If you are installing a JavaScript-only dependency that is used across both Web and Native platforms, install it within the packages/app directory to ensure availability in the shared logic:

    cd packages/app
    yarn add <package-name>
    cd ../..
    yarn
    cd packages/app
    yarn add date-fns
    cd ../..
    yarn
  5. Switch from App Router to Pages Router in Next.js

    main

    To migrate the Next.js application from the app router to the pages router, follow these steps:

    1. Remove the app folder from apps/next.
    2. Move index.tsx from pages-example to the pages folder.
    3. Rename pages-example-user to user.
    4. Update the linkTarget in screen.tsx to user.
    5. Delete the SwitchRouterButton.tsx component and remove its references from screen.tsx and packages/ui/src/index.tsx.
    6. Search for the pagesMode keyword and remove it.
  6. Deploy the Next.js app to Vercel

    main

    When deploying the web application to Vercel, use the following configuration:

    • Root Directory: apps/next
    • Install Command: yarn set version stable && yarn install
    • Build Command: Leave as default
    • Output Directory: Leave as default
  7. Initialize a new Tamagui starter project

    main

    To create a new project using the Tamagui starter template, use the following command:

    npm create tamagui

    Note: The maintainers highly recommend using Takeout instead, as it is a more comprehensive and actively maintained starter available in free and pro versions.

  8. Configure Tamagui with custom tokens via @my/ui

    main

    To ensure that your custom theme, tokens, and settings are available via the TamaguiCustomConfig interface in the tamagui package, the @my/ui package performs module augmentation.

    When you import from @my/ui, it applies the Conf type from @my/config to the tamagui module. This allows you to use your custom design tokens (e.g., specific colors, spacing, or sizes defined in @my/config) with full TypeScript autocomplete and type safety when using Tamagui components.

    // This augmentation happens automatically when importing from @my/ui
    // It allows code like this to be type-safe:
    import { Button } from '@my/ui';
    
    function App() {
      return <Button color="myCustomColor" /> // 'myCustomColor' is recognized via TamaguiCustomConfig
    }
  9. Configure Playwright for the Next.js app

    main

    The apps/next/playwright.config.ts file defines the end-to-end (e2e) testing configuration for the Next.js application. It is configured to run tests in parallel, handle retries in CI environments, and automatically manage a local development server for testing.

    Key Configuration Settings

    • Test Directory: Tests are located in ./e2e.
    • Parallelism: fullyParallel is enabled to run tests in parallel.
    • CI Behavior:
      • forbidOnly: Disallows .only in test cases when process.env.CI is set.
      • retries: Performs 2 retries in CI, otherwise 0.
      • workers: Limits to 1 worker in CI to prevent resource contention.
      • reuseExistingServer: Set to true in local environments to avoid starting a new server if one is already running.
    • Base URL: The application is expected to be running at http://localhost:3000.
    • Tracing: Playwright traces are captured on-first-retry.
    • Web Server: The configuration automatically starts the app using yarn dev and waits for http://localhost:3000 to be ready before running tests.
    import { defineConfig, devices } from '@playwright/test'
    
    export default defineConfig({
      testDir: './e2e',
      fullyParallel: true,
      forbidOnly: !!process.env.CI,
      retries: process.env.CI ? 2 : 0,
      workers: process.env.CI ? 1 : undefined,
      reporter: 'html',
      use: {
        baseURL: 'http://localhost:3000',
        trace: 'on-first-retry',
      },
      projects: [
        {
          name: 'chromium',
          use: { ...devices['Desktop Chrome'] },
        },
      ],
      webServer: {
        command: 'yarn dev',
        url: 'http://localhost:3000',
        reuseExistingServer: !process.env.CI,
      },
    })
  10. Configure Tamagui with custom fonts and animations

    main

    The Tamagui configuration is created using createTamagui. This starter template extends the @tamagui/config/v5 default configuration with custom fonts, animations, and specific settings.

    Key configuration areas:

    • animations: Overrides the default animations with animationsApp.
    • fonts: Defines custom body and heading fonts using local font definitions.
    • settings: Configures Tamagui behavior. In this template, onlyAllowShorthands is set to false to allow full property names in addition to shorthands.

    By declaring TamaguiCustomConfig, the configuration is typed globally, providing full TypeScript autocompletion for your custom theme, fonts, and tokens throughout the application.

    import { defaultConfig } from '@tamagui/config/v5'
    import { createTamagui } from 'tamagui'
    import { bodyFont, headingFont } from './fonts'
    import { animationsApp } from './animationsApp'
    
    export const config = createTamagui({
      ...defaultConfig,
      animations: animationsApp,
      fonts: {
        body: bodyFont,
        heading: headingFont,
      },
      settings: {
        ...defaultConfig.settings,
        onlyAllowShorthands: false,
      },
    })
    
    export type Conf = typeof config
    
    declare module 'tamagui' {
      interface TamaguiCustomConfig extends Conf {}
    }
  11. Configure Metro for the Expo app in a monorepo

    main

    The apps/expo/metro.config.js file is configured to support a monorepo structure and Tamagui v2 requirements. It extends the default Expo Metro configuration with the following settings:

    • Watch Folders: The workspaceRoot is added to watchFolders to ensure Metro tracks changes across the entire monorepo.
    • Node Modules Resolution: resolver.nodeModulesPaths is configured to look in both the local apps/expo/node_modules and the monorepo workspaceRoot/node_modules.
    • Package Exports: unstable_enablePackageExports is set to true, which is required for Tamagui v2 subpath imports.
    • Condition Names: unstable_conditionNames is set to ['require', 'react-native', 'import'] to ensure native conditions are correctly resolved for packages using exports.
    • Transformer Settings: unstable_allowRequireContext is enabled, and the minifier is explicitly set to metro-minify-terser.
    // Learn more https://docs.expo.io/guides/customizing-metro
    /**
     * @type {import('expo/metro-config')}
     */
    const { getDefaultConfig } = require('expo/metro-config')
    const path = require('path')
    
    const projectRoot = __dirname
    const workspaceRoot = path.resolve(__dirname, '../..')
    
    const config = getDefaultConfig(projectRoot)
    
    config.watchFolders = [workspaceRoot]
    config.resolver.nodeModulesPaths = [
      path.resolve(projectRoot, 'node_modules'),
      path.resolve(workspaceRoot, 'node_modules'),
    ]
    
    // enable package exports - required for tamagui v2 subpath imports
    config.resolver.unstable_enablePackageExports = true
    
    // ensure native conditions are resolved for packages using exports
    config.resolver.unstable_conditionNames = ['require', 'react-native', 'import']
    
    config.transformer = { ...config.transformer, unstable_allowRequireContext: true }
    config.transformer.minifierPath = require.resolve('metro-minify-terser')
    
    module.exports = config
  12. Implement a NativeToast component using @tamagui/toast

    main

    In this monorepo, NativeToast is a component used to render toast notifications when they are not being handled by the native platform. It utilizes useToastState from @tamagui/toast to access the current toast's properties like id, duration, viewportName, title, and message.

    If currentToast.isHandledNatively is true, the component returns null to avoid overlapping with native system toasts. The component uses the Toast primitive with specific enterStyle and exitStyle for animations.

    import { Toast, useToastState } from '@tamagui/toast'
    import { YStack } from 'tamagui'
    
    export const NativeToast = () => {
      const currentToast = useToastState()
    
      if (!currentToast || currentToast.isHandledNatively) {
        return null
      }
    
      return (
        <Toast
          key={currentToast.id}
          duration={currentToast.duration}
          viewportName={currentToast.viewportName}
          enterStyle={{ opacity: 0, scale: 0.5, y: -25 }}
          exitStyle={{ opacity: 0, scale: 1, y: -20 }}
          y={0}
          opacity={1}
          scale={1}
          transition="quick"
        >
          <YStack py="$1.5" px="$2">
            <Toast.Title lineHeight="$1">{currentToast.title}</Toast.Title>
            {!!currentToast.message && <Toast.Description>{currentToast.message}</Toast.Description>}
          </YStack>
        </Toast>
      )
    }