React Native is amazing for building mobile apps, but sharing components between web and native sacrifices performance, productivity and UX.
Tamagui introduces a novel optimizing compiler for React that evaluates your code at build-time, turning heavy JS into flatter trees and much faster CSS. The result is more code share %, less dev time, and lighter, faster apps.
Tamagui's optimizing compiler makes advanced optimizations using partial evaluation, CSS extraction, and ultimately tree-flattening. Where it optimizes, things get a lot faster:
Tamagui
0.02ms
react-native-web
0.063ms
Dripsy
0.108ms
NativeBase
0.73ms
Tamagui comes in three parts:
Tamagui Core (@tamagui/core
) is a style library for React Native and web that supports 100% of the React Native API surface in a library that only depends on React. It adds all the goodies of modern style libraries into a cohesive package.
Tamagui Static (@tamagui/static
) is an optimizing compiler on top of Core, turning even inline styles sprinkled with logic into atomic CSS and minimal JS, flattening your view tree for maximum performance.
Tamagui UI (tamagui
) is a component kit built on top of the incredibly powerful theme primitives of core
. That, alongside composable-component APIs and complete size control enables a new level of control not seen before in a UI kit.
Overview
Lets get familiar with both @tamagui/core
(the styling library) and @tamagui/static
(the compiler), and how the latter optimizes the former.
A powerful `styled` constructor, inline props, shorthands and more.
import { Stack } from '@tamagui/core'
import { Heading } from './Heading'
const App = (props) => (
<Stack px="$2" w={550} $gtSm={{ px: '$6' }}>
<Heading size={props.big ? 'large' : 'small'}>Lorem ipsum.</Heading>
</Stack>
)
Styles extracted, logic evaluated, and a flatter tree with atomic CSS styles per-file.
export const App = props => <div className={_cn}>
<h1 className={_cn2 + (_cn3 + (props.big ? _cn4 : _cn5))}>
Lorem ipsum.
</h1>
</div>
const _cn5 = " _fos-16px"
const _cn4 = " _fos-22px"
const _cn3 = " _bg-180kg62 _col-b5vn3b _mt-0px _mr-0px _mb-0px _ml-0px _ww-break-word _bxs-border-box _ff-System _dsp-inline "
const _cn2 = " font_System"
const _cn = " is_Stack _fd-column _miw-0px _mih-0px _pos-relative _bxs-border-box _fb-auto _dsp-flex _fs-0 _ai-stretch _w-550px _pr-1aj14ca _pl-1aj14ca _pr-_gtSm_lrpixp _pl-_gtSm_lrpixp"
The key to Tamagui's performance gains lie in view flattening or tree-flattening: the compiler analyzes code (even across module boundaries) and evaluates styled components away using both the AST and Node VM code evaluation. It will fully flatten custom styled components (like <Circle />
) into plain old <div />
(or <View />
on native).
👋 Hey! Listen!
Tamagui is fully OSS, self-funded and built by me.
My goal is to support Tamagui development with sponsorships that get early access to some really interesting new features.
Overview of @tamagui/core
Core is a complete cross-platform style library. It allows creating typed design systems that work with a styled
factory on both React Native and web:
Highlights
- Works 100% the same across Native and Web.
- Works completely at runtime, no compiler necessary, but optimizes away at compile-time.
- Size of 22Kb
@tamagui/web
to 31Kb@tamagui/core
(gzip, excluding react). - SSR, React Server Component, Concurrent mode support for 100% of the features.
- Custom typed design systems.
- Powerful typed variants, including functional variants.
- Prop-order based styling means no more CSS specificity fights!
- Inline styles with tokens, media and pseudo styles that all optimize away.
- Typed shorthands.
- Pluggable animation drivers, allows swapping JS-based animations to pure CSS on the web.
One novel feature is deterministic, easy to reason about style merging. Core always merges styles based on the order received rather than arcane CSS definition-order + specificity rules, no matter if those styles come through nested styled
calls, or via inline props. This resolves limitations in both CSS and CSS-in-JS systems as they largely exist today.
styled()
Here's an example of the styled
function with a few variants:
import { Stack, styled } from 'tamagui' // or '@tamagui/core'export const Circle = styled(Stack, {// access your tokens and theme values easily with $ propsbackgroundColor: '$background',borderRadius: '$4',// media and pseudo styles - this would take 15+ lines of brittle JS in RN$gtSm: {pressStyle: {borderRadius: '$6',},},variants: {// define variants <Circle pin="top" />// these will flatten, even when nesting multiple styled() callspin: {top: {position: 'absolute',top: 0,},},size: {// functional variants give incredible power and save bundle size'...size': (size, { tokens }) => {return {width: tokens.size[size] ?? size,height: tokens.size[size] ?? size,}},},} as const,})
For more, see the configuration, styling, and variants docs.
Design system
Using createTamagui you generate a fully-typed design system with colors, tokens, spacing, sizing, fonts, themes, shorthands, animations, and more.
It works with well-optimized and easy to use hooks like useMedia and useTheme that save a ton of time over rolling these yourself.
Here's a stripped down configuration:
import { createFont, createTamagui, createTokens } from 'tamagui' // or '@tamagui/core'const interFont = createFont({family: 'Inter, Helvetica, Arial, sans-serif',size: { 1: 12, 2: 14 /* ... */ },// ... lineHeight, weight, letterSpacing, transform, style, color, face})const size = { 0: 0, 1: 5, 2: 10 /* ... */ }const tokens = createTokens({size,space: { ...size, '-1': -5, '-2': -10 },radius: { 0: 0, 1: 3 },zIndex: { 0: 0, 1: 100, 2: 200 },color: { white: '#fff', black: '#000' },})export default createTamagui({fonts: {heading: interFont,body: interFont,},tokens,themes: {light: { bg: '#f2f2f2', color: tokens.color.black },dark: { bg: '#111', color: tokens.color.white },},media: {sm: { maxWidth: 860 },gtSm: { minWidth: 860 + 1 },},shorthands: {px: 'paddingHorizontal',f: 'flex',} as const,})
Stack and Text
Finally, the Stack and Text components work with styled()
and provide lot of nice features like hover, press, and focus styles, typed inline style props with theme and token values, shorthands, media queries, animations, and more.
What's new in @tamagui/core
Over a thousand bugfixes since beta with large performance and memory improvements, much higher test coverage, and compatibility with the latest and greatest in React and React Native.
Compatibility
Across all of Tamagui, we've bumped support to the latest versions. Due to improvements in react-native-web 18, we were also able to dramatically simplify setup, no longer having to patch the library to support classnames.
Tamagui supports React Native 0.70, React 18, React Native Web 18. It's the only Native UI library that fully supports concurrent mode on the web as well.
Big gains for web-only use cases
Early in the beta for web-only use cases, Core had the downside of requiring you to bundle all of react-native-web
in your app, not to mention the always tricky problem of configuring your bundler to alias react-native
to react-native-web
.
For 1.0, we've made the difficult steps to get @tamagui/core
to have no dependency at all on react-native
(and therefore no need to setup anything in your bundler), while still maintaining 100% compatibility with the React Native APIs.
This unlocks a few things:
- Much easier setup: no need to touch your bundler.
- Save ~30Kb of bundle size by avoiding
react-native-web
entirely for web-only use cases.
You can get all the benefits of unified APIs across native and web plus an optimizing compiler and full-featured design system, yet actually save bundle size vs React Native Web.
Bundle size reduction
De-coupling core from react-native
is a big win for web bundle size, allowing you to leave out 35Kb of size if just using core
. We've shaved nearly ~12Kb beyond that during the betas by iteratively improving code and reducing dependencies. Today core is ~20Kb, gzipped, but can get down to ~10Kb fairly straightforwardly.
Beyond that, we have landed refactors now that allow releasing a web-specific core which will drop a few Native-specific APIs and bring the total runtime size cost down to below 8Kb.
Performance improvements
We've greatly optimized render performance during the beta across the board. Some of the more interesting improvements are:
useTheme
The useTheme
hook is used by every Tamagui component, and is smart about re-renders by tracking which keys are accessed by your styles to only re-render when their values change. But it wasn't as smart about memory usage and avoiding work up front, and was also re-parenting the React tree too often.
We saw 8% improvements in Lighthouse scores on the website homepage just by optimizing this.
useMedia
Supporting SSR often means you need to hydrate using an initial value, and then re-render after hydration with the "real" media query value. We've moved over to use React's useSyncExternalStore hook , which greatly reduced code and helps avoid re-renders and hooks used per-component. It also clears us to remove the double-render that SSR based apps typically do on hydration.
Avoiding work generating styles
Previously, Core used react-native-web
to handle the final steps of taking styles and converting them into CSS. In profiling and reading over the code, we found this to be a large bottleneck to performance as it would iterate over objects many times, and generate many intermediate objects in the process.
@tamagui/core
is now entirely dependency-free and generates it's own styles. In total, de-coupling saved us 4-5 loops over the generated style objects per-component.
styled
upgrades
The styled
factory has undergone a number of improvements. It now supports wrapping any component you give it, so long as that component accepts a style
prop.
A note on styled
types
We now recommend using as const
after your variants
object definition to fix some tricky issues related to some outstanding Typescript limitations around inferring const generics.
Fonts
<FontLanguage />
Supporting fonts per-language is now possible with <FontLanguage />, like so. First, you can specify language-specific fonts with a suffix in your configuration, much like a sub-theme:
const bodyFontEn = createFont({family: '"Helvetica"',// ... per-font design tokens})const bodyFontMandarin = createFont({family: '"Helvetica Mandarin"',// ... per-font design tokens})export const config = createTamagui({fonts: {body: bodyFontEn,body_mandarin: bodyFontMandarin,},})
Then you can change the family to Mandarin at any point in your React tree, with types working as expected:
import { FontLanguage, Text } from 'tamagui' // or '@tamagui/core'export default () => (<FontLanguage body="mandarin"><Text fontFamily="$body">{/* TODO hello world in mandarin */}</Text></FontLanguage>)
config.fonts.[fontname].face
React Native makes loading fonts a bit trickier than the web, and the easiest way to do it involved naming your font family differently per-weight. On the web you'd have Helvetica
and just change font-weight
, but on Native you set the family from Helvetica
to Helvetica Bold
instead of the weight.
Tamagui added support for this through the face
option on font configurations.
The themeShallow
prop
The theme
prop in Tamagui by default will re-theme all sub-component. Tamagui now supports the boolean themeShallow
prop on any Tamagui component, which will only theme that exact component, leaving all children components with the same theme as their parent.
Helpers
The loadTheme
and updateTheme
helpers
Themes load in a large variety of tokens for spacing, sizing, radius, colors, and more. They are incredibly powerful, but they also have some cost in bundle size.
The loadTheme
utility function means you can only load the default themes you want to serve, saving on bundle size, and then add in alternative themes later on.
Meanwhile updateTheme
gives you the ability to dynamically modify themes on the fly, changing any of their values. On the web, themes work through CSS variables, meaning updateTheme
is incredibly fast as it avoids all React re-rendering if no theme values are currently being relied on for dynamic styles.
The useMediaPropsActive
hook
This hook is useful for authoring your own custom components built on Tamagui. The Tamagui UI kit makes use of this hook extensively. Tamagui has a philosophy that everything Just Works™️ at runtime as well as compile-time. The compile-time side just makes it all a lot faster. By working fully at runtime, it means you have more flexiblity (no need to use any plugins at all), but also lots of power. For example, if Tamagui didn't work fully runtime, you couldn't support animations with your design system at all.
The useMediaPropsActive
is an important part of that functionality, making it easy to properly access the "currently active" set of styles given the current screen size. Here's an example:
import { Stack, StackProps, useMediaPropsActive } from 'tamagui' // or '@tamagui/core'const CustomWidget = (props: StackProps) => {const activeProps = useMediaPropsActive(props)console.log(`The current color for this screen size is`, activeProps.backgroundColor)return <Stack {...modifyProps(activeProps)} />}export default () => (<CustomWidget backgroundColor="red" $large={{ backgroundColor: 'green', }} />)
Overview of @tamagui/static
Static is a babel and node-based optimizing compiler, specifically for frontend React code. It supports web and native through a plugin interface that has adapters for Webpack, Vite, Next, React Native, and Expo. Over a dozen major improvements to the compiler have made their way in since beta. Some of the highlights are:
On the web, render performance is much improved, especially for interactive (pseudo) and responsive (media query) styles. It improves Lighthouse scores 10-25%.
For more information on how it works and why it's important, read our introduction to the compiler article.
What's new in @tamagui/static
Performance
The compiler uses babel
to parse and optimize components, which it can get away with for performance largely because it only needs to parse a subset of your files, and it only needs to look for a few areas - namely JSX elements and styled()
functions.
During the beta we landed many performance improvements bringing average parse time down to below 6ms for a longer file on a modern laptop. By default now Tamagui avoids parsing many more files through quick heuristics. It's now also fully thread-safe, uses more caching, and has been fine-tuned for memory usage.
Coverage
Previously, Tamagui Static only knew how to optimize components found in your separated design system package. But it's both common and desirable to have one-off styled()
definitions that are just used for small areas of your app that live alongside those areas, outside your design system.
The compiler now supports analyzing components outside of just your design system, allowing for even less friction when writing apps. It means you can put your styled()
definitions anywhere without worrying, and Tamagui Static will load and optimize those components as it discovers them.
Vite support
Tamagui now fully works with Vite 3 and 4, both with the compiler and without. A simple vite.config.js
would look like this:
import { tamaguiExtractPlugin, tamaguiPlugin } from '@tamagui/vite-plugin'import react from '@vitejs/plugin-react-swc'import { defineConfig } from 'vite'const tamaguiCompilerConfig = {components: ['tamagui'],config: 'tamagui.config.ts',useReactNativeWebLite: true,}export default defineConfig({clearScreen: false,plugins: [react(),tamaguiPlugin(tamaguiCompilerConfig),tamaguiExtractPlugin(tamaguiCompilerConfig),],})
Correctness
Most of the work during beta focused on correctness. We've increased testing to cover many more areas, and landed a number of important correctness fixes alongside the ones in core
.
Overview of Tamagui
Tamagui UI is a suite of React components and hooks that adapt nicely to both Native and Web.
- Every component works on Native and Web and adapts properly to each platform.
- Lightweight default styles included but completely customizable.
- Complete theme control - totally transform the look and feel down to individual components without fuss, even avoiding re-renders on theme changes (web).
- Complete size control - every component can be scaled up/down based on your custom design system tokens.
- Composable Component APIs - enabling much more control over styling.
- A novel Adapt component allows for completely changing UI based on platform and media query.
What's new in Tamagui
A whole lot of new components 🎁
We've seen the addition of seven new components. Each comes with composable component API for much more complete control over each piece of the components visuals and behavior.
We owe thanks to both Radix and Floating UI for making these much easier to build.
Like the existing components in Tamagui, every new component is sizable, themeable, and able to be automatically styled using your design system.
The new components are:
Sheet
This was an excellent test of the unified animation drivers on Tamagui, and unified interaction primitives of React Native, and we're happy with how it's turned out.
Internally, we've come up with some helpers for each animation driver to support a consistent API for taking a number value and interpolating it with some style properties. Each driver handles this quite differently on the surface, but the Tamagui drivers now give a consistent interface across them all.
Select
Dialog
Show content in a Dialog that can adapt to another component depending on the screen size.
AlertDialog
Show a Dialog specialized for confirming or denying an action with AlertDialog.
AlertDialog is the first of our components to include the native
prop. When set to true, it adapts the element to use platform native visual containers when possible. For AlertDialog, this means that on iOS and Android, you'll see a native Alert dialog prompt.
Slider
A draggable Slider allows users to input values within a range.
Label
Label has been updated to work with all the new form inputs.
Card
Display content with a header, footer, background image, title, subtitle and description using Card.
ListItem
ListItem allows you to display content with a title, subtitle, before and after images or icons, and more.
Avatar
Spinner
Progress
XGroup and YGroup
The Group
component has been split into XGroup and YGroup to match Stacks. It's been upgraded across the board with the ability to be scrollable and to better handle passing children styles.
ScrollView
Tamagui now exports ScrollView, the exact same as React Native ScrollView but with all Tamagui props supported.
What else is new
Apps
The Kitchen Sink
The monorepo now includes apps/kitchen-sink
, a native app that demos every component that comes in Tamagui. This has helped us quickly iterate and improve components and their correctness across Android and iOS. Android especially received a wide variety of fixes during the betas.
Starter repos
create-tamagui
underwent some big improvements that will set it up to be much more useful going forward. We moved starters into the monorepo, which was tricky but lets us iterate much more rapidly on them and test them e2e for each release. We're using a custom ~/.tamagui
home directory and git clone that gives us a much smoother upgrade experience, especially in future versions. The starer-free
starter repo has had extensive polish as well, simplifying the setup, removing the need for watch commands, and properly building to production across all features for web, iOS and Android.
Features
React Native Animation Driver
We added a new animation driver, @tamagui/animations-react-native
that works with the built-in Animation API of React Native. This is in addition to the existing @tamagui/animations-css
and @tamagui/animations-reanimated
drivers. The advantages of this driver are that you pay no extra cost for spring animations on the web when you're already using react-native-web
, and a simpler setup compared to Reanimated.
SSR and React Server Components
During the beta we landed a wide variety of correctness fixes, tests and runtime safeguards for SSR and landed early support for React Server Components. We've tested them in early versions of Hydrogen and Next.js and they should generally work when the environment variable ENABLE_RSC
is set.
Vite Plugin + Vite Compiler
We've landed Vite support. Check out the Vite guide for how to install Tamagui with Vite.
react-native-web-lite
As a temporary measure, we released a modernized version of react-native-web
. It improves tree shaking, lands complete concurrent support for React 18, and strips out a few deprecated areas.
Concurrent Mode Support
We've updated Tamagui's base React version to be 18, and landed a large amount of fixes relating to concurrent mode. Every feature in Tamagui now fully supports concurrent mode, and with react-native-web-lite
, so does every feature in the React Native API surface for web.
@tamagui/next-theme
See the Next.js guide for more on how to use this helpful library that automatically handles light/dark/system color scheme preferences.
@tamagui/theme-base
upgrades
The default theme package now includes _active
sub-themes, paving the way for a consistent way to style all active states across every component in tamagui
.
It also now includes theme values for color1
=> color12
as part of each theme. This gives you granular access outside of the more specific color values like background
, or borderColor
.
Usage in production
Tamagui now has several apps in production in both app stores. One larger one built by a household name company is currently under NDA, but we can announce soon. We're proud of how active the Discord has become with users building high quality apps!
Polish
Correctness
More than any other section in this release, the biggest amount of effort for 1.0 went into correctness. Tamagui has steadily landed fixes across every feature and component since Beta, and the difference between early this year and now is astounding, with a ton of edge cases handled automatically now.
Community
We've been building a great community, especially on Discord . Check out the Community page for more information including diverse community-maintained starter kits and Figma files.
Documentation
The docs have undergone continuous improvements. The compiler now has an extensive article breaking down the whys and hows of how it works. And all the guides have been iterated on and tested from scratch to verify they work.
Testing
We've expanded our testing signficantly: 10x more tests, package.json linting, full CI/CD, stricter linting in general, and a few big integration tests.
Going forward
Tamagui is stable and free of all blocking bugs across every feature, but there's a so much more to go to make every part of it simpler, lighter, faster, and more correct both in types and behavior.
Writing about what comes after 1.0 is a blog post to itself. For now, next.md is a living document of upcoming features and fixes.
The next few minor releases will focus on improving types, accessibility, animations, test coverage, and solidifying the newer components. We'll be setting up stricter release processes, and improving contributor experience.
There are many exciting features on the table as well, including:
- Improving dynamic inline evaluation.
- Container queries.
- Descendent styling ala
a:hover p
. tamagui build
- build to plain css and JS for compatibility with any bundler.- Adding the
native
prop for platform-specific output on Select, Dialog, Popover, Sheet and more. <Theme />
custom tokens.<Debug />
to show nice visualizations of what's happening inside an entire tree of components.<Skeleton />
to automatically render skeleton variants for every component in a tree.<Menu />
,<Radio />
or<Toggle />
equivalent,<Tabs />
,<Accordion />
,<Autocomplete />
and more.- Adding pseudo
focusWithinStyle
. - Breakpoints as token values allowing
height="$lg"
.
Sponsors
Tamagui is fully funded by Github Sponsors.
I love working on Tamagui, and there's a wealth of concrete and interesting ways for it to mature. The motivation is to take things slowly, do them right, and stay fully OSS.
In order to make this work, I'll need sponsors, and I want to make that worth it. I'm setting up a range of pretty cool Sponsor Benefits going forward:
- New features as they are being developed.
- Early access to source code.
- Nightlies, access to a private npm org.
- Private chat at higher levels.
- Early access to upcoming Studio, Docs and other apps:
Your design system and UI auto-generates a beautiful docs site, just like tamagui.dev.
A frontend realtime editor for colors, themes, tokens, default styles and more.
A simpler way to build universal apps.
Docs can run alongside Studio in the future as part of a suite of tools for your frontend. Studio will have realtime collaboration and allows you to edit your apps themes live with HMR. Tweak colors, themes, tokens, styles, icons, fonts, and more across every pseudo state on every component.
Studio hooks into your app at runtime to achieve this thanks to the big work we've aleady done pre-analyzing your components and their types and props.
It will also export and import between JSON and Figma.
Please do consider sponsoring!
Acknowledgements
We've had 38 new contributors since early this year, exponentially increasing! Thanks to all of them, and also to the many QA testers that join in our Discord chat every day :).
Early sponsors have made a huge difference: I owe a large thank you to Vercel , Codingscape , QuestPortal and BeatGig , the first corporate sponsors ❤️.
Tamagui draws inspiration and small areas of code from some incredible other libraries: Radix provided much of the composable component APIs, Floating UI handles all of the floating elements positioning, Moti which served as a start point for the animation drivers, and Framer Motion for the nifty AnimatePresence functionality.