Tamagui Beta

Animations, a new theme system, starter apps, and much more.

Nate Wienert

16 min read

Tamagui is a universal UI kit for React Native and Web, making it much easier to ship performant, beautiful apps across all platforms using the same code. It makes hard things easy - like themes, responsive styling, and animation.

At its core are three libraries:

  • A style library (@tamagui/core) for creating React Native + Web components.
  • An optimizing compiler (@tamagui/static) for incredible performance.
  • A component kit (tamagui) built using both of the above.
  • It's been six months since the first alpha, several hundreds of bugfixes , three demo apps , ten new components, and now a few big new core features. Plus, a refreshed site, a benchmark suite, a host of type improvements, and compatibility with more versions of Next.js, Webpack, and React Native.

    What's new?

    Get started faster 🚀

    Introducing create-tamagui , to get started much faster with a bootstrapped monorepo that uses Expo, Next.js, Solito and Tamagui:

    npm create tamagui

    A big shout out to Fernando Rojo  for creating Solito , a great library for sharing all your views between Expo and Next.js, and the bootstrap repo.

    wwwwwwwwwwwwwwwwwww

    Themes 2.0 🎨

    Theme internally have been completely redone, and they're really fun. They work better, nest better, and come with a nice preset in @tamagui/themes.

    Theme objects now default to names that map to standard style names (with optional pseudo suffixes), so color, plus the alternates colorHover, colorFocus, and colorPress are definable.

    Building off that, themes can nest unlimited times - dark and light can have dark_blue and light_blue, and then different tints below that, like dark_blue_alt1.

    Billie Jean

    Michael Jackson

    Thriller

    Billie Jean

    Michael Jackson

    Thriller

    Billie Jean

    Michael Jackson

    Thriller

    Billie Jean

    Michael Jackson

    Thriller

    Billie Jean

    Michael Jackson

    Thriller

    Billie Jean

    Michael Jackson

    Thriller

    The theme prop is now also available on every component, including ones you create using the styled factory function.

    Components now apply any sub-theme that matches their name - so Button, will look for dark_blue_Button if it exists, giving complete control the UI at the theme level, across every component. PLus, icons, buttons and more components have been upgraded to better handle passing themes down automatically.

    I'm really happy with how this landed. This release could have focused more on new components, but it became obvious while working on Tooltip / Popover / Sheet that getting animations and themes right needed to happen first to avoid future pain.

    We now have a very solid foundation.

    Read the docs for much more on themes.

    wwwwwwwwwwwwwwwwwww

    Animations

    Animations are making their way, experimentally, into @tamagui/core. They are unstable, but we want to get feedback and use them more in practice.

  • BouncyA bouncy spring
  • LazyA lazy, straightforward spring
  • QuickA super fast spring
  • damping

    9

    mass

    0.9

    stiffness

    150

    They're implemented as pluggable drivers, starting with two:

    Which means you can swap entire animation drivers depending on your platform. Want lighter bundles on web, but spring animations on native? Install both packages and swap them out based on your environment.

    We've also added a new pseudo enterStyle that works if you have an animation prop set, giving you easy styling on mount (and see the next section for exitStyle).

    Read the docs on animations to get started.

    AnimatePresence

    In early form, we've added AnimatePresence, which is directly forked off Framer Motion. It works with exitStyle and variants to make once-complex animations quite easy.

    import { AnimatePresence } from '@tamagui/animate-presence'
    import { ArrowLeft, ArrowRight } from '@tamagui/lucide-icons'
    import { useState } from 'react'
    import { Button, Image, XStack, YStack, styled } from 'tamagui'
    // @ts-ignore
    import photo1 from '../../public/photo1.jpg'
    // @ts-ignore
    import photo2 from '../../public/photo2.jpg'
    // @ts-ignore
    import photo3 from '../../public/photo3.jpg'
    export const images = [photo1, photo2, photo3].map((x) => x.src || x)
    const GalleryItem = styled(YStack, {
    zIndex: 1,
    x: 0,
    opacity: 1,
    fullscreen: true,
    variants: {
    // 1 = right, 0 = nowhere, -1 = left
    going: {
    ':number': (going) => ({
    enterStyle: {
    x: going > 0 ? 1000 : -1000,
    opacity: 0,
    },
    exitStyle: {
    zIndex: 0,
    x: going < 0 ? 1000 : -1000,
    opacity: 0,
    },
    }),
    },
    } as const,
    })
    const wrap = (min: number, max: number, v: number) => {
    const rangeSize = max - min
    return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min
    }
    export function AnimationsPresenceDemo() {
    const [[page, going], setPage] = useState([0, 0])
    const imageIndex = wrap(0, images.length, page)
    const paginate = (going: number) => {
    setPage([page + going, going])
    }
    return (
    <XStack overflow="hidden" backgroundColor="#000" position="relative" height={300} width="100%" alignItems="center" >
    <AnimatePresence initial={false} custom={{ going }}>
    <GalleryItem key={page} animation="slowest" going={going}>
    <Image source={{ uri: images[imageIndex], width: 820, height: 300 }} />
    </GalleryItem>
    </AnimatePresence>
    <Button accessibilityLabel="Carousel left" icon={ArrowLeft} size="$5" position="absolute" left="$4" circular elevate onPress={() => paginate(-1)} zi={100} />
    <Button accessibilityLabel="Carousel right" icon={ArrowRight} size="$5" position="absolute" right="$4" circular elevate onPress={() => paginate(1)} zi={100} />
    </XStack>
    )
    }
    wwwwwwwwwwwwwwwwwww

    New Components

    We've added quite a few new components, and a good portion more are nearing release. Since the alpha we've introduced: Inputs, Label, Switch, Image, Separator, ThemeInverse, Popover, Tooltip, Drawer, Square and Circle, HTML Elements.

    Here's the new Switch component working with spring animations:

    import type { SizeTokens } from 'tamagui'
    import { Label, Separator, Switch, XStack, YStack, styled } from 'tamagui'
    export function SwitchDemo() {
    return (
    <YStack width={200} alignItems="center" gap="$3">
    <XStack gap="$3" $xs={{ flexDirection: 'column' }}>
    <SwitchWithLabel size="$2" />
    <SwitchWithLabel size="$2" defaultChecked />
    </XStack>
    <XStack gap="$3" $xs={{ flexDirection: 'column' }}>
    <SwitchWithLabel size="$3" />
    <SwitchWithLabel size="$3" defaultChecked />
    </XStack>
    <XStack gap="$3" $xs={{ flexDirection: 'column' }}>
    <SwitchWithLabel size="$4" />
    <SwitchWithLabel size="$4" defaultChecked />
    </XStack>
    </YStack>
    )
    }
    export function SwitchWithLabel(props: { size: SizeTokens; defaultChecked?: boolean }) {
    const id = `switch-${props.size.toString().slice(1)}-${props.defaultChecked ?? ''}}`
    return (
    <XStack width={200} alignItems="center" gap="$4">
    <Label paddingRight="$0" minWidth={90} justifyContent="flex-end" size={props.size} htmlFor={id} >
    Accept
    </Label>
    <Separator minHeight={20} vertical />
    <Switch id={id} size={props.size} defaultChecked={props.defaultChecked}>
    <Switch.Thumb animation="quicker" />
    </Switch>
    </XStack>
    )
    }

    Join our Discord to vote for new components .

    wwwwwwwwwwwwwwwwwww

    Styled Factory Upgrades

    The styled() factory function as well gets many upgrades, it accepts React Native components directly and automatically interprets their props, as well as many fixes - full media query support, much faster/accurate types, and better compilation.

    wwwwwwwwwwwwwwwwwww

    Popover

    Popover was introduced in alpha, but we've redone it for the beta. It's still early stage, but the API is significantly improved. It now gives you much more control over the target, content and trigger. Importantly, it works using Tamagui primitives, and automatically wraps your content in AnimatePresence supporting beautiful and simple enter/exit animations.

    The core of Popover is now powered by the wonderful Floating UI , saving us a lot of complexity and giving it more reliable and powerful behaviors.

    It's also been split out optionally into it's own package at @tamagui/popover.

    Show the code below to see the improved component-based API.

    wwwwwwwwwwwwwwwwwww

    Tooltip

    Tooltip likewise was rebuilt in a similar fashion to Popover - a composable component API, full animation support, and powered by Floating UI's React DOM Interactions .

    wwwwwwwwwwwwwwwwwww

    Other Component Updates

    LinearGradient, Image, and Button have received lots of love. LinearGradient now fully supports all theme values and the compiler will help it extract any style properties. Image should still be considered beta, but has gone through many rounds of bugfixes and improvements. And Button now handles passing more text properties down, themes and sizes icons more reliably, and has many fixes for variants like circular.

    wwwwwwwwwwwwwwwwwww

    Focus Styles

    We landed experimental support for the focusStyle prop, which works much like pressStyle and hoverStyle. You can see it in action on Input:

    import type { SizeTokens } from 'tamagui'
    import { Button, Input, TextArea, XStack, YStack } from 'tamagui'
    export function InputsDemo() {
    return (
    <YStack width={200} minHeight={250} overflow="hidden" space="$2" margin="$3" padding="$2" >
    <InputDemo size="$2" />
    <InputDemo size="$3" />
    <InputDemo size="$4" />
    <TextArea placeholder="Enter your details..." />
    </YStack>
    )
    }
    function InputDemo(props: { size: SizeTokens }) {
    return (
    <XStack alignItems="center" space="$2">
    <Input flex={1} size={props.size} placeholder={`Size ${props.size}...`} />
    <Button size={props.size}>Go</Button>
    </XStack>
    )
    }
    wwwwwwwwwwwwwwwwwww

    ThemeReset

    Sometimes you want to reset the current theme to the grandparent. ThemeReset does just this. Note: it's not SSR compatible if you use automatic light/dark themes (it will still work, but it won't fully render until JS runs). There's a fix for this that is a bit involved, but we think it's still useful to release it as experimental.

    wwwwwwwwwwwwwwwwwww

    Theme Generation

    Use the themes from the Tamagui site itself without any setup at all, simply install @tamagui/themes and add themes and tokens to createTamagui() in your tamagui.config.ts.

    Likewise, we've released @tamagui/shorthands, nice preset default shorthands.

    See config-base itself an example of this pattern .

    wwwwwwwwwwwwwwwwwww

    Feather Icons

    We've released @tamagui/lucide-icons, which have numerous adjustments to work well with themes and sizing.

    You can import and use them directly, or pass them to Buttons:

    import { Play } from '@tamagui/lucide-icons'
    import { Button } from 'tamagui'
    // theme and size are properly passed from Button to icons automatically
    export default () => <Button size="$6" icon={Play} />
    wwwwwwwwwwwwwwwwwww

    And much more...

    • ✨ Many docs improvements: new pages, more examples, more props documented, and search.
    • asChild prop that lets you simply pass all style props down to a child.
    • ✨ Support for media query prefers color scheme
    • 🐛 Hundreds of bug-fixes .
    • 🐛 Much improved install experience and many bugs fixed with Next, Webpack, Monorepos.
    wwwwwwwwwwwwwwwwwww

    Breaking changes

    • Sub themes now must be named with _ (underscore) separators.
    • In tamagui.config.ts, tokens.font => fonts.
    • Popover and Tooltip are redone in with Radix-style nested components
      • We've exported a small TooltipSimple component that mimics the old Tooltip
    • HoverablePopover has been removed in favor of just Tooltip or customizing Popover
    wwwwwwwwwwwwwwwwwww

    Going forward

    There's a ton of progress here, but likewise a ton more to go. There's many components, docs, and features to be written, and alongside that I'd like to get better at community and sharing progress from here on out.

    What really excites me with this release is the foundation laid. Tamagui now has such a rich set of primitives - from animations, themes, and sizing, to performance across every feature, to all the helpers and hooks adopted since alpha that make animated, sizable and Radix-like composable API's a breeze, all the way to Floating UI and all the powerful primitives that brings. Everything feels like it's working together well (✊🪵).

    Seeing Tooltip come to life somewhat easily in the latest refactor by just importing a few hooks/components and giving it an animation prop with AnimatePresence was really inspiring.

    For the the first time I feel like building and shipping an actually beautiful, cross platform app, without needing huge funding is within reach. That's cool.

    🍻

    wwwwwwwwwwwwwwwwwww

    Acknowledgements

    This release once again is inspired by or forks code from a variety of other libraries and developers.

    I'd like to thank:

    • Fenando Rojo  and Moti  for the elegant reanimated animation driver that served as the starting point of @tamagui/animations-reanimated, as well as for putting together Solito  that powers the first template in create-tamagui.
    • Matt Perry  and Framer Motion  for the base of AnimatePresence in @tamagui/animate-presence.
    • Radix  for their rad quality, the innovative composing components API's, and asChild.

    Share this post on Twitter.