Styling

Style props, conditional values, and the modifiers that control them

Tamagui uses style props across all components. Every element accepts React Native and web styles, with unified support for responsive, theme, and pseudo-state conditions.

import { Text, View } from 'tamagui'
export const Card = () => (
<View padding="4 md:6" borderRadius="lg" backgroundColor="background hover:background-hover" >
<Text color="color-11">Hello</Text>
</View>
)

padding, borderRadius, and backgroundColor are style props. "4" and "lg" are tokens, background is a theme key, and md: and hover: are modifiers. That is the whole grammar. The same props go on View, Text, every other component, and the second argument of styled().

Every style prop takes its value as a string or an object. Strings fit the base value and its conditions into one short read:

<View padding="4 md:8" backgroundColor="background hover:background-hover" />

Objects spell the same thing with keys, and stay fully typed:

<View padding={{ default: '4', md: '8' }} backgroundColor={{ default: 'background', hover: 'background-hover' }} />

Use strings for short, mostly static values — a prop with one or two conditions, written inline. They exist so the common case stays terse and readable. Use objects when the value is a number (numbers skip token lookup, so padding={16} is always 16 pixels), when conditions stack up, or when you build the value in code from variables and spreads — objects are real values, so TypeScript checks every token and modifier as you type. Both spellings take the same values and modifiers and render identically, so mix them per prop as needed. The Conditional values section below details each spelling.

Values

A style value is one of:

ValueExampleResolves to
Numberpadding={16}16 pixels on every platform
Pixel stringpadding="16px"An exact pixel value, even when a token has the same name
Tokenpadding="4", borderRadius="lg"The token from that prop’s category
Theme keycolor="color-11"The active theme’s value, updating when the theme changes
CSS valuedisplay="flex", boxShadow="0 2px 8px shadow-3"Passed through on web and converted on native
rempadding="1rem"Scaled by the user’s font size on native
Value
Number
Example
padding={16}
Resolves to
16 pixels on every platform
Value
Pixel string
Example
padding="16px"
Resolves to
An exact pixel value, even when a token has the same name
Value
Token
Example
padding="4", borderRadius="lg"
Resolves to
The token from that prop’s category
Value
Theme key
Example
color="color-11"
Resolves to
The active theme’s value, updating when the theme changes
Value
CSS value
Example
display="flex", boxShadow="0 2px 8px shadow-3"
Resolves to
Passed through on web and converted on native
Value
rem
Example
padding="1rem"
Resolves to
Scaled by the user’s font size on native

Design system covers where tokens and theme keys come from. Style props lists every prop, including the ones that only render on web.

Conditional values

Every style prop takes conditions in two spellings, a string of clauses or a flat object. These render the same thing:

<View backgroundColor="red hover:blue sm:green sm:hover:purple" />

The object keys are the same modifiers the string uses, with default for the base value. There is no mode to pick, and you can mix them per prop in one component:

<View backgroundColor="red hover:blue" scale={{ press: 0.98 }} />

Strings are shorter for one or two conditions. Objects work better when the values are numbers, when the conditions stack up, or when you build the value in code. Both compile to identical CSS.

An object only counts as conditional when it has a default key or its first key is a valid modifier. A plain value object like shadowOffset={{ width: 0, height: 2 }} is left alone.

Font size and line height values

Numeric lineHeight authored in Tamagui styles is a font-size ratio on web and native. A numeric string has the same ratio meaning when it is not a configured font-token name. An explicit px string stays absolute. Values are never inferred from magnitude, so lineHeight={24} means 24 times the effective font size and lineHeight="24px" means 24 pixels.

Font configuration keeps a separate compatibility contract. Numeric lineHeight entries remain absolute pixels. Use a numeric string for a relative font token and a px string for an explicit absolute token:

<Text fontSize={17}>Platform line height</Text>
<Text fontSize="17px">Exact px size</Text>
<Text fontSize={20} lineHeight={1.5}>30px line box</Text>
<Text lineHeight="24px">24px line box</Text>
const bodyFont = createFont({
family: 'Inter',
size: { body: 20 },
lineHeight: {
legacy: 24, // absolute pixels
relative: '1.5', // ratio
explicit: '24px', // absolute pixels
},
})

On native, Tamagui can resolve a ratio from the effective font size inherited through Tamagui text. This inheritance continues through intervening Tamagui Views, as it does on web. When a Tamagui text boundary sits below a raw React Native or third-party text parent, pass the effective font size at that boundary because Tamagui cannot inspect typography held only by the native host tree.

If a native ratio has no known font size, Tamagui explicitly sets fontSize={14} and calculates the line height from 14. This also applies below a raw native text parent, so pass its font size at the Tamagui boundary to preserve the glyph size.

Modifiers

Every modifier applies to just the property carrying it. They fall into five groups:

GroupModifiersExample
Statehover:, press:, focus:, focus-visible:, focus-within:, disabled:scale="press:0.98"
Screen sizeany media name from your config, such as sm: or max-md:padding="4 md:8"
Themedark:, light:, or a top-level theme namecolor="dark:white"
Platformweb:, native:, ios:, android:paddingTop="ios:12"
Parentgroup-hover:, group-press:, group-focus:, @sm: container queriescolor="group-hover:white"
Group
State
Modifiers
hover:, press:, focus:, focus-visible:, focus-within:, disabled:
Example
scale="press:0.98"
Group
Screen size
Modifiers
any media name from your config, such as sm: or max-md:
Example
padding="4 md:8"
Group
Theme
Modifiers
dark:, light:, or a top-level theme name
Example
color="dark:white"
Group
Platform
Modifiers
web:, native:, ios:, android:
Example
paddingTop="ios:12"
Group
Parent
Modifiers
group-hover:, group-press:, group-focus:, @sm: container queries
Example
color="group-hover:white"

Modifiers stack. sm:hover:purple applies on hover at the sm breakpoint. When several clauses match, the most specific one wins. Flat conditional values has the full precedence rules and grammar.

States

Add state clauses directly to the property they change:

  • hover:: while hovering (web only, maps to CSS :hover)
  • press:: while pressing
  • focus:: while focused (maps to CSS :focus)
  • focus-visible:: while keyboard-focused (maps to CSS :focus-visible)
  • focus-within:: while a child has focus (maps to CSS :focus-within)
  • disabled:: while disabled={true}
<View backgroundColor="background hover:background-hover press:background-press" scale="press:0.98" outlineColor="focus-visible:blue-500" outlineWidth="focus-visible:2px" outlineStyle="focus-visible:solid" borderColor="focus-within:blue-500" opacity="disabled:0.5" />

hover: is web only. focus:, focus-visible:, and focus-within: map to the matching CSS pseudo-classes on web.

Enter and exit

For mount and unmount animations, enter: is the value the property animates from on mount, and exit: is the value it animates to on unmount:

<View opacity="enter:0 exit:0" />

See Animations.

Screen size

Media queries come from createTamagui({ media }). Each name is a modifier on native and web:

<Text color="red sm:blue" />

Theme and platform

Theme modifiers target top-level themes. With light and light_subtle defined, only light can be targeted. Platform modifiers cover ios, android, web, and native:

<Text color="dark:white ios:white" />

Groups

Mark a parent with group and children can react to its state:

<View group>
<Text color="gray group-hover:white" />
</View>

The states are hover (web only), press, and focus. Name a group to target it from further down:

<View group="card">
<View group>
<Text color="gray group-hover/card:blue">Inner</Text>
<Text color="gray group-hover:green">Sibling</Text>
</View>
</View>

Inner turns blue when the card group is hovered. Sibling turns green when its nearest group is hovered. For completion on named groups, declare them next to your config types:

declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
interface TypeOverride {
groupNames(): 'card' | 'header' | 'sidebar'
}
}

Container queries

Add container to a group and children can respond to the parent’s size, using the same media names. Web outputs a CSS container query:

<View group container>
<Text color="gray @sm:white @sm:group-hover:green" />
</View>

Named containers work the same way with @sm/card:.

On native the container’s size is only known after the first onLayout, so children render once before the query applies. Set untilMeasured="hide" on the container to keep it at opacity 0 until measured, or give the container an explicit width and height so children can resolve the query on first render.

Next