wwwwwwwwwwwwwwwwwww

Style API

The Tamagui superset of React Native styles

Tamagui supports a superset of the React Native style properties - either to the styled() function as the second argument, or directly as props on the View and Text base components.

Here's how that looks in practice:

import { View, styled } from '@tamagui/core'
const StyledView = styled(View, {
padding: 10,
})
const MyView = () => (
<StyledView backgroundColor="red" hoverStyle={{ backgroundColor: 'green' }} />
)

The types for the full set of styles accepted by styled, View and Text are exported as ViewStyle and TextStyle.

For the full base styles, see the React Native docs:

The full Tamagui typed style props can be simplified to something like this, except the values can accept "unset" or one of your design tokens:

import { ViewStyle as RNViewStyle } from 'react-native'
type BaseViewStyle = RNViewStyle & FlatTransformStyles & WebOnlyStyles
// these are accepted but only render on web:
type WebOnlyStyles = {
contain?: Properties['contain']
touchAction?: Properties['touchAction']
cursor?: Properties['cursor']
outlineColor?: Properties['outlineColor']
outlineOffset?: SpaceValue
outlineStyle?: Properties['outlineStyle']
outlineWidth?: SpaceValue
userSelect?: Properties['userSelect']
}
// these turn into the equivalent transform style props:
type FlatTransformStyles = {
x?: number
y?: number
perspective?: number
scale?: number
scaleX?: number
scaleY?: number
skewX?: string
skewY?: string
matrix?: number[]
rotate?: string
rotateY?: string
rotateX?: string
rotateZ?: string
}
// add the pseudo and enter/exit style states
type WithStates = BaseViewStyle & {
hoverStyle?: BaseViewStyle
pressStyle?: BaseViewStyle
focusStyle?: BaseViewStyle
enterStyle?: BaseViewStyle
exitStyle?: BaseViewStyle
}
// final View style props
type ViewStyle = WithStates & {
// add media queries
$sm?: WithStates
// add group queries
$group-tabs?: WithStates
$group-tabs-hover?: WithStates
$group-tabs-focus?: WithStates
$group-tabs-press?: WithStates
// add group + container queries
$group-tabs-sm?: WithStates
$group-tabs-sm-hover?: WithStates
$group-tabs-sm-focus?: WithStates
$group-tabs-sm-press?: WithStates
// add theme queries
$theme-light?: WithStates
$theme-dark?: WithStates
// add platform queries
$platform-native?: WithStates
$platform-ios?: WithStates
$platform-android?: WithStates
$platform-web?: WithStates
}
// Text style starts with this base but builds up the same:
type TextStyleBase = BaseViewStyle & {
color?: string,
fontFamily?: string,
fontSize?: string,
fontStyle?: string,
fontWeight?: string,
letterSpacing?: string,
lineHeight?: string,
textAlign?: string,
textDecorationColor?: string,
textDecorationLine?: string,
textDecorationStyle?: string,
textShadowColor?: string,
textShadowOffset?: string,
textShadowRadius?: string,
textTransform?: string,
}

Parent based styling

Tamagui has a variety of ways to style a child based on the "parent", a parent being one of: platform, screen size, theme, or group. All of these styles use the same pattern, they use a $ prefix for their styles, and they nest styles as a sub-object.

For example you can target $theme-light, $platform-ios, or $group-header. For screen size, which we call media queries, they have no prefix. Instead you define media queries on createTamagui. For example, if you define a media query named large, then $large is the prop name.

These parent style props accept all the Tamagui style props in their value object.

Media query

Based on whatever media queries you define in createTamagui, you can now use any of them to apply styling on native and web using the $ prefix.

If you defined your media query like:

createTamagui({
media: {
sm: { maxWidth: 800 },
},
})

Then you can use it like:

<Text color="red" $sm={{ color: 'blue' }} />

Theme

Theme style props let you style a child based on a parent theme. At the moment, they only can target your top level themes, so if you have light, and light_subtle themes, then only light can be targeted.

Use them like so:

<Text $theme-dark={{ color: 'white' }} />

Platform

Platform style props let you style a child based on the platform the app is running on. This can be one of ios, android, web, or native (ios and android).

Use it like so:

<Text $platform-ios={{ color: 'white' }} />

Group

Groups are a new feature in beta that lets you define a named group, and then style children based whether they are inside a parent that is given that group name.

A short example:

<View group="header">
<Text $group-header={{ color: 'white' }} />
</View>

This will make the Text turn white, as it's inside a parent item with group set to the matching header value.

Group styles also allow for targeting the parent pseudo state:

<View group="card">
<Text $group-card-hover={{ color: 'white' }} />
</View>

Now only when the parent View is hovered the Text will turn white. The allowed psuedo modifiers are hover (web only), press, and focus.

To make this typed, you need to set TypeOverride alongside the same area you set up your Tamagui types:

declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
// if you want types for group styling props, define them like so:
interface TypeOverride {
groupNames(): 'a' | 'b' | 'c'
}
}

Group Container

The final feature of group styles is the ability to style a child only when the parent is of a certain size. On the web these are known as "container queries", which is what Tamagui outputs as CSS under the hood. They look like this:

<View group="card">
<Text $group-card-sm={{ color: 'white' }} $group-card-sm-hover={{ color: 'green' }} />
</View>

Now the Text will be white, but only when the View matches the media query sm. This uses the same media query breakpoints you defined in createTamagui({ media }). You can see it also works with pseudo styles!

A note on group containers and native

On Native, we don't have access to the layout of a React component as it first renders. Only once we get the dimensions from the onLayout callback after the first render are we able to apply group container styles.

Because of this, we've done two things.

First, there's a new property untilMeasured:

<View group="card" untilMeasured="hide">
<Text $group-card-sm={{ color: 'white', }} />
</View>

This takes two options, show or hide. If unset it defaults to show, which means it will render before it measures. With hide set, the container will be set to opacity 0 until it finishes measuring.

Alternatively, if you know the dimensions your container will be up-front, you can set width and height on the container. When either of these are set, the children will attempt to use these values on first render and if they satisfy the media query, you'll avoid the need for a double render altogether.

Style order is important

On thing that's very important to understand in Tamagui is that style props are sensitive to their order - a feature that without which would leave us with impossible styling challenges and awkward rules of inheritence we're trying to get away from with CSS in JS. Let's first explain how it works, and then why it's necessary.

Define a new styled component:

const CalHeader = styled(Text, {
variants: {
isHero: {
true: {
fontSize: 36,
backgroundColor: 'blue',
color: 'white',
},
},
},
})

And then use it in a view you're building:

export const MyCalendar = (props: { isHero?: boolean; headerFontSize?: number }) => {
return (
<>
{/* ... some other components... */}
<CalHeader isHero={props.isHero} fontSize={props.headerFontSize}>
{monthName}
</CalHeader>
</>
)
}

Why it's important

Notice two things: isHero sets a variety of properties, but you want to allow overriding one of those properties, fontSize.

If Tamagui didn't respect the order of the props on the JSX element of CalHeader, you wouldn't know if the isHero font size wins, or if the headerFontSize wins.

CSS does a "last defined style wins", which is a huge pain because it means you have to carefully manage the order your CSS is actually loaded into the document. Tamagui could do something similar, with a "inline props always win, defined props go in order of definition", but this would be dramatically less flexible.

Especially when it comes to spreading props downwards. Because Tamagui supports prop order, you have complete control over which styles you want to always win, vs which can be overridden by a user:

export const MyCalendarHeader = (props: CalHeaderProps) => {
return (
<CalHeader isHero {...props} fontSize={36}>
{monthName}
</CalHeader>
)
}

This component defaults to isHero styles, but if a user passes in isHero as false, it will disable all those styles. But because fontSize is always after the spread, it will always be set to 36.

Previous

Props

Next

styled()