Use Twin’s tw prop to add Tailwind classes onto jsx elements:
import 'twin.macro'
const Component = () => (
<div tw="flex w-full">
<div tw="w-1/2"></div>
<div tw="w-1/2"></div>
</div>
)
- Use the tw prop when conditional styles aren’t needed
- Any import from
twin.macro
activates the tw prop - Remove the need for an import with babel-plugin-twin
To add conditional styles, nest the styles in an array and use the css
prop:
import tw from 'twin.macro'
const Component = ({ hasBg }) => (
<div
css={[
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
]}
>
<div tw="w-1/2" />
<div tw="w-1/2" />
</div>
)
TypeScript example
import tw from 'twin.macro'
interface ComponentProps {
hasBg?: string
}
const Component = ({ hasBg }: ComponentProps) => (
<div
css={[
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
]}
>
<div tw="w-1/2" />
<div tw="w-1/2" />
</div>
)
- Twin doesn’t own the css prop, the prop comes from your css-in-js library
- Adding values to an array makes it easier to define base styles, conditionals and vanilla css
- Use multiple lines to organize styles within the backticks (template literals)
Use the tw
prop after the css prop to add any overriding styles:
import tw from 'twin.macro'
const Component = () => (
<div css={tw`text-white`} tw="text-black">
Has black text
</div>
)
It’s no secret that when tailwind class sets become larger, they obstruct the readability of other jsx props.
To clean up the jsx, lift the styles out and group them as named entries in an object:
import tw from 'twin.macro'
const styles = {
container: ({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
],
column: tw`w-1/2`,
}
const Component = ({ hasBg }) => (
<section css={styles.container({ hasBg })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
TypeScript example
import tw from 'twin.macro'
interface ContainerProps {
hasBg?: boolean;
}
const styles = {
container: ({ hasBg }: ContainerProps) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
],
column: tw`w-1/2`,
}
const Component = ({ hasBg }: ContainerProps) => (
<section css={styles.container({ hasBg })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
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:
import tw 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 styles = {
container: ({ variant = 'dark' }) => [
tw`flex w-full`,
containerVariants[variant], // Grab the variant style via a prop
],
column: tw`w-1/2`,
}
const Component = ({ variant }) => (
<section css={styles.container({ variant })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
TypeScript example
Use the TwStyle
import to type tw blocks:
import tw, { TwStyle } from 'twin.macro'
type WrapperVariant = 'light' | 'dark' | 'crazy'
interface ContainerProps {
variant?: WrapperVariant
}
const containerVariants: Record<WrapperVariant, 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 styles = {
container: ({ variant = 'dark' }: ContainerProps) => [
tw`flex w-full`,
containerVariants[variant], // Grab the variant style via a prop
],
column: tw`w-1/2`,
}
const Component = ({ variant }: ContainerProps) => (
<section css={styles.container({ variant })}>
<div css={styles.column} />
<div css={styles.column} />
</section>
)
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:
<div tw="mt-${spacing === 'sm' ? 2 : 4}" /> // Won't work with tailwind classes
<div tw="[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 from 'twin.macro'
const styles = { sm: tw`mt-2`, lg: tw`mt-4` }
const Component = ({ spacing = 'sm' }) => <div css={styles[spacing]} />
Or combine vanilla css with twins theme
import:
import { theme } from 'twin.macro'
// Use theme values from your tailwind config
const styles = { sm: theme`spacing.2`, lg: theme`spacing.4` }
const Component = ({ spacing = 'sm' }) => (
<div css={{ marginTop: styles[spacing] }} />
)
Or we can always fall back to vanilla css, which can interpolate anything:
import 'twin.macro'
const Component = ({ width = 5 }) => <div css={{ maxWidth: `${width}rem` }} />
Use square-bracketed arbitrary variants to style elements with a custom selector:
import tw from 'twin.macro'
const buttonStyles = tw`
bg-black
[> i]:block
[> span]:(text-blue-500 w-10)
`
const Component = () => (
<button css={buttonStyles}>
<i>Icon</i>
<span>Label</span>
</button>
)
More examples
// Style the current element based on a theming/scoping className
;<body className="dark-theme">
<div tw="[.dark-theme &]:(bg-black text-white)">Dark theme</div>
</body>
// Add custom group selectors
;<button className="group" disabled>
<span tw="[.group:disabled &]:text-gray-500">Text gray</span>
</button>
// Add custom height queries
;<div tw="[@media (min-height: 800px)]:hidden">
This window is less than 800px height
</div>
// Use custom at-rules like @supports
;<div tw="[@supports (display: grid)]:grid">A grid</div>
// Style the current element based on a dynamic className
const Component = ({ isLarge }) => (
<div className={isLarge && 'is-large'} tw="text-base [&.is-large]:text-lg">
...
</div>
)
Custom values can be added to many tailwind classes by using square brackets to define the custom value:
;<div tw="top-[calc(100vh - 2rem)]" />
// ↓ ↓ ↓ ↓ ↓ ↓
<div css={{
"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
<div tw="[--my-width-variable:calc(100vw - 10rem)]" />
// Set vendor prefixes
<div tw="[-webkit-line-clamp:3]" />
// Set grid areas
<div tw="[grid-area:1 / 1 / 4 / 2]" />
Use arbitrary properties with variants or twins grouping features:
<div tw="block md:(relative [grid-area:1 / 1 / 4 / 2])" />
Arbitrary properties also work with the tw
import:
import tw from 'twin.macro'
;<div
css={tw`
block
md:(relative [grid-area:1 / 1 / 4 / 2])
`}
/>
- 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 css prop accepts a sass-like syntax, allowing both custom css and tailwind styles with values that can come from your tailwind config:
import tw, { css, theme } from 'twin.macro'
const Components = () => (
<input
css={[
tw`text-blue-500 border-2`,
css`
-webkit-tap-highlight-color: transparent; /* add css styles */
background-color: ${theme`colors.red.500`}; /* use the theme import to add config values */
&::selection {
${tw`text-purple-500`}; /* style with tailwind classes */
}
`,
]}
/>
)
But it’s often cleaner to use an object to add styles as it avoids the interpolation cruft seen above:
import tw, { css, theme } from 'twin.macro'
const Components = () => (
<input
css={[
tw`text-blue-500 border-2`,
css({
WebkitTapHighlightColor: 'transparent', // css properties are camelCased
backgroundColor: theme`colors.red.500`, // values don’t require interpolation
'&::selection': tw`text-purple-500`, // single line tailwind selector styling
}),
]}
/>
)
- Styled component guide - A must-read guide on getting productive with styled-components
- 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