code/tamagui.dev/data/docs/core/styled.mdx
Create a new component by extending an existing one:
import { GetProps, View, styled } from '@tamagui/core'
export const RoundedSquare = styled(View, {
borderRadius: 20,
})
// helper to get props for any TamaguiComponent
export type RoundedSquareProps = GetProps<typeof RoundedSquare>
Usage:
<RoundedSquare x={10} y={10} backgroundColor="red" />
You can pass any prop that is supported by the component you are wrapping in styled.
One really important and useful thing to note about Tamagui style properties: the order is important! Read more here
Let's add some variants:
import { View, styled } from '@tamagui/core'
export const RoundedSquare = styled(View, {
borderRadius: 20,
variants: {
pin: {
top: {
position: 'absolute',
top: 0,
},
},
centered: {
true: {
alignItems: 'center',
justifyContent: 'center',
},
},
size: {
'...size': (size, { tokens }) => {
return {
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
}
},
},
} as const,
})
We can use these like so:
<RoundedSquare pin="top" centered size="$lg" />
To learn more about how to use them and all the special types, see the docs on variants.
You can assume all "utility" views in React Native are not supported: Pressable, TouchableOpacity, and others. They have specific logic for handling events that conflicts with Tamagui. We could support these in the future, but we don't plan on it - you can get all of Pressable functionality for the most part within Tamagui itself, and if you need something outside of it, you can use Pressable directly.
The styled() function supports Tamagui views, React Native views, and any other React component that accepts a style prop. If you wrap an external component that Tamagui doesn't recognize, Tamagui will assume it only supports the style prop and not optimize it.
If it does accept className, you can opt-in to className, CSS media queries, and compile-time optimization by adding acceptsClassName:
import { SomeCustomComponent } from 'some-library'
import { styled } from 'tamagui' // or '@tamagui/core'
export const TamaguiCustomComponent = styled(SomeCustomComponent, {
acceptsClassName: true,
})
The render prop lets you control which element or component is rendered. It accepts three forms:
Render as a specific HTML element on web while maintaining native View on mobile:
const Button = styled(View, {
render: 'button',
padding: '$4',
backgroundColor: '$background',
})
const Anchor = styled(Text, {
render: 'a',
color: '$blue10',
})
const Nav = styled(View, {
render: 'nav',
})
This is the recommended approach for semantic HTML and accessibility. String render props are optimized by the Tamagui compiler.
Pass a JSX element to clone with Tamagui's computed props:
<Stack
render={<a href="/about" target="_blank" />}
padding="$4"
backgroundColor="$background"
>
About Page
</Stack>
The JSX element's props are merged with Tamagui's computed styles, classNames, and event handlers. This is useful when you need to pass element-specific props like href or target.
For full control, pass a render function that receives props and component state:
import { TamaguiComponentState } from '@tamagui/core'
;<Stack
render={(props, state: TamaguiComponentState) => (
<CustomButton {...props} isHovered={state.hover} isPressed={state.press} />
)}
padding="$4"
hoverStyle={{ backgroundColor: '$blue5' }}
>
Custom Button
</Stack>
The state object includes:
hover - true when hovered (web)press - true when pressedfocus - true when focuseddisabled - true when disabledYou can also override the render prop at runtime:
const Box = styled(View, {
padding: '$4',
})
// Use as a button
<Box render="button">Click me</Box>
// Use as a link
<Box render="a" href="/about">About</Box>
Any component created with styled() has a new static property on it called .styleable().
If you want a functional component that renders a Tamagui-styled component inside of it to also be able to be styled(), you need to wrap it with styleable. This is a mouthful, let's see an example:
// 1. you create a `styled` component as usual:
const StyledText = styled(Text)
// 2. you create a wrapper component that adds some logic
// but still returns a styled component that receives the props:
const HigherOrderStyledText = (props) => <StyledText {...props} />
// 3. you want that wrapper component itself to be able to use with `styled`:
const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
variants: {
// oops, variants will merge incorrectly
},
})
The above code will generally cause weird issues, because Tamagui can't know that it needs to just forward some props down. Instead, Tamagui tries to "resolve" all the style props from StyledHigherOrderStyledText before passing them down to HigherOrderStyledText. But that causes problems, because now HigherOrderStyledText will merge things differently than you'd expect.
The way to fix this is to add .styleable() around your HigherOrderStyledText. You'll also want to forward the ref, which is forwarded for you:
const StyledText = styled(Text)
// note the styleable wrapper here:
const HigherOrderStyledText = StyledText.styleable((props, ref) => (
<StyledText ref={ref} {...props} />
))
const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
variants: {
// variants now merge correctly
},
})
Now your component will handle everything properly, even if a theme is changed on HigherOrderStyledText, it will be applied.
A final note: you must pass all Tamagui style props given to HigherOrderStyledText down to a single StyledText, at least if you want everything to work fully correctly.
And if you'd like to add new props on top of the existing props, you can pass them in for the first generic type argument of styleable:
import { View, ViewProps } from '@tamagui/core'
type ExtraProps = {
someCustomProp: boolean
}
export type CustomProps = ViewProps & ExtraProps
const Custom = View.styleable<ExtraProps>((props) => {
// ...
return null
})
If you are wrapping something like an SVG, you may want it to accept theme and token values on certain props, for example fill. You can do so using accept:
const StyledSVG = styled(
SVG,
{},
{
accept: {
fill: 'color',
} as const,
}
)
Now your StyledSVG will properly type the fill property to accept token and theme values and will pass the resolved colors to the SVG component.
You can also use accept to take in Tamagui style objects and output React Native style objects. This is useful for things like the contentContainerStyle prop on ScrollView, which expects a style object:
const MyScrollView = styled(
ScrollView,
{},
{
accept: {
contentContainerStyle: 'style', // or 'textStyle'
} as const,
}
)