Use Twin’s tw
import to create and style new components with Tailwind classes:
import tw from 'twin.macro'
const Wrapper = tw.section`flex w-full`
const Column = tw.div`w-1/2`
const Component = () => (
<Wrapper>
<Column></Column>
<Column></Column>
</Wrapper>
)
To add conditional styles, nest the styles in an array and use the styled
import:
import tw, { styled } from 'twin.macro'
const Container = styled.div(({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
])
const Column = tw.div`w-1/2`
const Component = ({ hasBg }) => (
<Container {...{ hasBg }}>
<Column></Column>
<Column></Column>
</Container>
)
TypeScript example
import tw, { styled } from 'twin.macro'
interface ContainerProps {
hasBg?: string
}
const Container = styled.div<ContainerProps>(({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
])
const Column = tw.div`w-1/2`
const Component = ({ hasBg }: ContainerProps) => (
<Container {...{ hasBg }}>
<Column></Column>
<Column></Column>
</Container>
)
- Adding styles in an array makes it easier to separate base styles, conditionals and vanilla css
- The
styled
import comes from the css-in-js library, twin just exports it - Use multiple lines to organize styles within the backticks (template literals)
When a variant has many values (eg: variant="light/dark/etc"
), name the class set in an object and use a prop to grab the entry containing the styles. Note that you must return a function as follows:
import tw, { styled } from 'twin.macro'
const containerVariants = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const Container = styled.section(() => [
// Return a function here
tw`flex w-full`,
({ variant = 'dark' }) => containerVariants[variant], // Grab the variant style via a prop
])
const Column = tw.div`w-1/2`
const Component = () => (
<Container variant="light">
<Column></Column>
<Column></Column>
</Container>
)
TypeScript example
Use the TwStyle
import to type tw blocks:
import tw, { styled, TwStyle } from 'twin.macro'
type ContainerVariant = 'light' | 'dark' | 'crazy'
interface ContainerProps {
variant?: ContainerVariant
}
// Use the `TwStyle` import to type tw blocks
const containerVariants: Record<ContainerVariant, TwStyle> = {
// Named class sets
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const Container = styled.section<ContainerProps>(() => [
// Return a function here
tw`flex w-full`,
({ variant = 'dark' }) => containerVariants[variant], // Grab the variant style via a prop
])
const Column = tw.div`w-1/2`
const Component = () => (
<Container variant="light">
<Column></Column>
<Column></Column>
</Container>
)
Due to Babel limitations, tailwind classes and arbitrary properties can’t have any part of them dynamically created.
So interpolated values like this won’t work:
const Component = styled.div(({ spacing }) => [
tw`mt-${spacing === 'sm' ? 2 : 4}`, // Won't work with tailwind classes
`[margin-top:${spacing === 'sm' ? 2 : 4}rem]`, // Won't work with arbitrary properties
])
This is because babel doesn’t know the values of the variables and so twin can’t make a conversion to css.
Instead, define the classes in objects and grab them using props:
import tw, { styled } from 'twin.macro'
const styles = { sm: tw`mt-2`, lg: tw`mt-4` }
const Card = styled.div(({ spacing }) => styles[spacing])
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
Or combine vanilla css with twins theme
import:
import { styled, theme } from 'twin.macro'
// Use theme values from your tailwind config
const styles = { sm: theme`spacing.2`, lg: theme`spacing.4` }
const Card = styled.div(({ spacing }) => ({ marginTop: styles[spacing] }))
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
Or you can always fall back to “vanilla css” which can interpolate anything:
import { styled } from 'twin.macro'
const Card = styled.div(({ spacing }) => ({
marginTop: `${spacing === 'sm' ? 2 : 4}rem`,
}))
const Component = ({ spacing = 'sm' }) => <Card spacing={spacing} />
You can use the tw
jsx prop to override styles in the styled-component:
import tw from 'twin.macro'
const Text = tw.div`text-white`
const Component = () => <Text tw="text-black">Has black text</Text>
Wrap components using the component extending feature to copy/override styles from another component:
import tw, { styled } from 'twin.macro'
const Container = tw.div`bg-black text-white`
// Extend with tw: for basic styling
const BlueContainer = tw(Container)`bg-blue-500`
// Or extend with styled: For conditionals
const RedContainer = styled(Container)(({ hasBorder }) => [
tw`bg-red-500 text-black`,
hasBorder && tw`border`,
])
// Extending more than once like this is not recommended
const BlueContainerBold = tw(BlueContainer)`font-bold`
const Component = () => (
<>
<Container />
<BlueContainer />
<RedContainer hasBorder />
</>
)
Reuse styled components with a different element using the as
prop:
import tw from 'twin.macro'
const Heading = tw.h1`font-bold` // or styled.h1(tw`font-bold`)
const Component = () => (
<>
<Heading>I am a H1</Heading>
<Heading as="h2">I am a H2 with the same style</Heading>
</>
)
Use square-bracketed arbitrary variants to style elements with a custom selector:
import tw from 'twin.macro'
const Button = tw.button`
bg-black
[> i]:block
[> span]:(text-blue-500 w-10)
`
const Component = () => (
<Button>
<i>Icon</i>
<span>Label</span>
</Button>
)
More examples
// Style the current element based on a theming/scoping className
const Theme = tw.div`[.dark-theme &]:(bg-black text-white)`
;<body className="dark-theme">
<Theme>Dark theme</Theme>
</body>
// Add custom group selectors
const Text = tw.div`[.group:disabled &]:text-gray-500`
;<button className="group" disabled>
<Text>Text gray</Text>
</button>
// Add custom height queries
const SmallHeightOnly = tw.div`[@media (min-height: 800px)]:hidden`
;<SmallHeightOnly>Burger menu</SmallHeightOnly>
// Use custom at-rules like @supports
const Grid = tw.div`[@supports (display: grid)]:grid`
;<Grid>A grid</Grid>
// Style the component based on a dynamic className
const Text = tw.div`text-base [&.is-large]:text-lg`
const Container = ({ isLarge }) => (
<Text className={isLarge ? 'is-large' : null}>...</Text>
)
Custom values can be added to many tailwind classes by using square brackets to define the custom value:
tw.div`top-[calc(100vh - 2rem)]`
// ↓ ↓ ↓ ↓ ↓ ↓
styled.div({ top: 'calc(100vh - 2rem)' })
Read more about Arbitrary values →
Basic css is added using arbitrary properties or within vanilla css which supports more advanced use cases like dynamic/interpolated values.
To add simple custom styling, use arbitrary properties:
// Set css variables
tw.div`[--my-width-variable:calc(100vw - 10rem)]`
// Set vendor prefixes
tw.div`[-webkit-line-clamp:3]`
// Set grid areas
tw.div`[grid-area:1 / 1 / 4 / 2]`
Use arbitrary properties with variants or twins grouping features:
tw.div`block md:(relative [grid-area:1 / 1 / 4 / 2])`
Use a theme value to grab a value from your tailwind.config:
tw.div`[color:theme('colors.gray.300')]`
tw.div`[margin:theme('spacing[2.5]')]`
tw.div`[box-shadow: 5px 10px theme('colors.black')]`
- Add a bang to make the custom css !important:
![grid-area:1 / 1 / 4 / 2]
- Arbitrary properties can have camelCase properties:
[gridArea:1 / 1 / 4 / 2]
The styled import accepts a sass-like syntax, allowing both custom css and tailwind styles with values that can come from your tailwind config:
import tw, { styled, css, theme } from 'twin.macro'
const Input = styled.div`
${css`
-webkit-tap-highlight-color: transparent; /* add css styles */
background-color: ${theme`colors.red.500`}; /* add values from your tailwind config */
${tw`text-blue-500 border-2`}; /* tailwind classes */
&::selection {
${tw`text-purple-500`}; /* style custom css selectors with tailwind classes */
}
`}
`
const Component = () => <Input />
- Prefix css styles with the
css
import to apply css highlighting in your editor - Add semicolons to the end of each line
It can be cleaner to use an object to add styles as it avoids the interpolation cruft seen in the last example:
import tw, { styled, theme } from 'twin.macro'
const Input = styled.div({
WebkitTapHighlightColor: 'transparent', // css properties are camelCased
backgroundColor: theme`colors.red.500`, // values don’t require interpolation
...tw`text-blue-500 border-2`, // merge tailwind classes into the container
'&::selection': tw`text-purple-500`, // allows single-line tailwind selector styling
})
const Component = () => <Input />
Mix tailwind classes and custom css in an array:
import tw, { styled } from 'twin.macro'
const Input = styled.div(({ tapColor }) => [
tw`block`,
`-webkit-tap-highlight-color: ${tapColor};`,
])
const Component = () => <Input tapColor="red" />
When you move the styles out of jsx, prefix them with the css
import:
import tw, { styled, css } from 'twin.macro'
const widthStyles = ({ tapColor }) => css`
-webkit-tap-highlight-color: ${tapColor};
`
const Input = styled.div(({ tapColor }) => [
tw`block`,
widthStyles({ tapColor }),
])
const Component = () => <Input tapColor="red" />
- Prop styling guide - A must-read guide to level up on prop styling
- babel-plugin-twin - Use the tw and css props without adding an import
- React + Tailwind breakpoint syncing - Sync your tailwind.config.js breakpoints with react
- Twin VSCode snippits - For devs who want to type less
- Twin VSCode extensions - For faster class suggestions and feedback