Twin blends the magic of Tailwind with the flexibility of css-in-js
Demo twin on CodeSandbox β
Style jsx elements using Tailwind classes:
import 'twin.macro'
const Input = () => <input tw="border hover:border-black" />
Nest Twinβs tw
import within a css prop to add conditional styles:
import tw from 'twin.macro'
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && tw`hover:border-black`]} />
)
Or mix sass styles with the css import:
import tw, { css } from 'twin.macro'
const hoverStyles = css`
&:hover {
border-color: black;
${tw`text-black`}
}
`
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && hoverStyles]} />
)
Styled Components
You can also use the tw import to create and style new components:
import tw from 'twin.macro'
const Input = tw.input`border hover:border-black`
And clone and style existing components:
const PurpleInput = tw(Input)`border-purple-500`
Switch to the styled import to add conditional styling:
import tw, { styled } from 'twin.macro'
const StyledInput = styled.input(({ hasBorder }) => [
`color: black;`,
hasBorder && tw`border-purple-500`,
])
const Input = () => <StyledInput hasBorder />
Or use backticks to mix with sass styles:
import tw, { styled } from 'twin.macro'
const StyledInput = styled.input`
color: black;
${({ hasBorder }) => hasBorder && tw`border-purple-500`}
`
const Input = () => <StyledInput hasBorder />
How it works
When babel runs over your javascript or typescript files at compile time, twin grabs your classes and converts them into css objects. These css objects are then passed into your chosen css-in-js library without the need for an extra client-side bundle:
import tw from 'twin.macro'
tw`text-sm md:text-lg`
// β β β β β β
{
fontSize: '0.875rem',
'@media (min-width: 768px)': {
fontSize: '1.125rem',
},
}
Features
+ import tw, { styled, css } from 'twin.macro'
- import tw from 'twin.macro'
- import styled from '@emotion/styled'
- import css from '@emotion/react'
β ml-7 was not found
Try one of these classes:
ml-0 [0] / ml-1 [0.25rem] / ml-2 [0.5rem] / ml-3 [0.75rem] / ml-4 [1rem] / ml-5 [1.25rem] / ml-6 [1.5rem]
ml-8 [2rem] / ml-10 [2.5rem] / ml-12 [3rem] / ml-16 [4rem] / ml-20 [5rem] / ml-24 [6rem] / ml-32 [8rem]
ml-40 [10rem] / ml-48 [12rem] / ml-56 [14rem] / ml-64 [16rem] / ml-auto [auto] / ml-px [1px]
- Prefix with
hocus:
to style hover + focus at the same time - Style form field states with
checked:
,invalid:
andrequired:
- Stack up variants whenever you need them
sm:hover:first:bg-black
Check out the full list of variants β
import 'twin.macro'
const interactionStyles = () => (
<div tw="hover:(text-black underline) focus:(text-blue-500 underline)" />
)
const mediaStyles = () => <div tw="sm:(w-4 mt-3) lg:(w-8 mt-6)" />
const pseudoElementStyles = () => <div tw="before:(block w-10 h-10 bg-black)" />
const stackedVariants = () => <div tw="sm:hover:(bg-black text-white)" />
const groupsInGroups = () => <div tw="sm:(bg-black hover:(bg-white w-10))" />
const setCssVariables = () => <div tw="--base-color[#C0FFEE]" />
const customGridProperties = () => <div tw="grid-area[1 / 1 / 4 / 2]" />
const vendorPrefixes = () => <div tw="-webkit-mask-image[url(mask.png)]" />
import { css, theme } from 'twin.macro'
const Input = () => <input css={css({ color: theme`colors.purple.500` })} />
See more examples using the theme import β
<div tw="hidden!" /> || <div tw="!hidden" />
// β β β β β β β β β
<div css={{ "display": "none !important" }} />
Add !important to multiple classes with bracket groups:
<div tw="(hidden ml-auto)!" />
// β β β β β β β β β
<div css={{ "display": "none !important", "marginLeft": "auto !important" }} />
Get started
Twin works within many modern stacks - take a look at these examples to get started:
App build tools and libraries
- Parcel
styled-components / emotion / emotion (ts) - Webpack
styled-components (ts) / emotion (ts) - Preact
styled-components / emotion / goober - Create React App
styled-components / emotion - Snowpack
styled-components / styled-components (ts) / emotion / emotion (ts) - Vite
styled-components (ts) / emotion (ts)
Advanced frameworks
- Gatsby
styled-components / emotion - Next.js
styled-components / emotion / emotion (ts)π / stitches (ts) - Blitz.js
emotion (ts)π - Laravel
styled-components (ts) - [email protected] (soon)
Component libraries
- Storybook
styled-components (ts) / emotion - yarn/npm workspaces + Next.js + shared ui components
styled-components - Yarn workspaces + Rollup
emotion - HeadlessUI (ts)
π
Community
Drop into our Discord server for announcements, help and styling chat.
Resources
-
π₯ Docs: The prop styling guide - A must-read guide to level up on prop styling -
π₯ Docs: The styled component guide - A must-read guide on getting productive with styled components - Docs: Options - Learn about the features you can tweak via the twin config
- Plugin: babel-plugin-twin - Use the tw and css props without adding an import
- Example: Advanced theming - Add custom theming the right way using css variables
- Example: React + Tailwind breakpoint syncing - Sync your tailwind.config.js breakpoints with react
- Helpers: Twin VSCode snippits - For devs who want to type less
- Plugins: VSCode plugins - VScode plugins that work with twin
- Article: "Why I Love Tailwind" by Max Stoiber - Max (inventor of styled-components) shares his thoughts on twin
Special thanks
This project stemmed from babel-plugin-tailwind-components so a big shout out goes to Brad Cornes for the amazing work he produced. Styling with tailwind.macro has been such a pleasure.
Tailwind v3 updates
We're currently testing the next major version of twin which syncs with tailwindcss v3
Help test the next release:
π₯ Bugs / Issue discussion β
Global styles get added in the wrong order when using styled-components
Hi,
I'm trying to upgrade a project to Tailwind v2 that also uses TailwindUI.
I'm using react with styled-components.
I'm running into problems with
@tailwindcss/forms
that renders my input fields differently depending wether I declare them in the same file as my App component or in a separate file. It seems like the order in the generatedstyle
element is not the same.I've built an example here: https://github.com/gligoran/twin-react-styled-components-bug (couldn't make it work on codesandbox: https://codesandbox.io/s/twin-react-styled-components-bug-bg82n).
The main part of the problem is this difference in order as you can see from these 2 screenshots:
Support tailwind plugins (e.g. custom-forms)
I tried to use https://tailwindcss-custom-forms.netlify.com but seems like it can't find the new classes.
Tailwind defaults are missing
Hi again @ben-rogerson in setting up Twin, I'm not seeing some important TW default styles: namely the opacity variables:
These are needed for theming with custom colors. See https://github.com/adamwathan/tailwind-css-variable-text-opacity-demo/issues/1#issuecomment-770595786 for more detail on that.
So, your docs don't say what the correct way to import defaults are, as far as I can see? I've tried a number of things. I'm using Gatsby and their tailwind setup docs say to add this import to the top-level file (gatsby-browser.js):
This unfortunately didn't add the opacity resets. I also notice you omit the
@tailwind
directives css file that Tailwind recommends, often calledtailwind.css
or just added toglobal.css
in some examples:Since you omit the latter in your Code Sandbox example I figure we don't need it, but I think we do need some sort of global base css import to get things to work right. If I manually add the two opacity variables to my global css things work correctly, but I imagine there are a lot of other defaults I don't know about yet as well.
Solution? Thanks!
ReferenceError: styled is not defined
In an otherwise well-working project with gatsby + styled-components (using
tw
prop,css
prop, andtw
import is working fine), I randomly can't get thestyled
import to work and getReferenceError: styled is not defined
if I try.The error doesn't come up always. First I've managed to get it to work somehow. After that, I could get it to fail again with above error by:
Sorry I can't pin it down more precisely.
The setup: gatsby 2.26.1, styled-components 5.2.1, twin.macro 1.12.1. (But playing with some earlier twin version didn't appear to change the behaviour). Running in a yarn workspaces setting if that matters.
package.json:
I've tried replacing the preset with
but that didn't change the outcome.
Btw: I love the twin project, your pace of adding new goodies is addictive..
String interpolation inside tw macro doesn't work
Hi!
I have a problem with string interpolation in tw macro. For example I need to customize border color, so I'm trying to do it like this:
But I get the following error:
β Class βhover:border-β shouldnβt have a trailing dash.
It also doesn't work without variables:
Issues pre-bundling twin components with Rollup and importing them
Hey,
This may be a dumb question.
If i publish a package to NPM ... say button that i use this macro within and then publish. If i then import this package with the button would this still work?
for example...
nextjs site
should that work or am i missing the point the macros in that it is only compile time... I use rollup for button example
styled-components: _cssPropImport is not defined
Hey,
Q: Why is emotion necessary with CRA?
The top of the readme there is the following simple way which is absolutely amazing at a first sight.
Furthermore, there is also this sentence which would give me the impression that without conditionals I can use macro only.
Unfortunately, when I try that in a clean CRA project, the output I get is like this and that's where the magical feeling is kinda lost.
To make that work I have to include this ceremony in each file that needs some styling.
Is there some better way I am simply not seeing?
Error "Identifier '_styled' has already been declared" with styled-components + ts + next.js
Looks like the compiler duplicates the import statement of
styled-components
with the one created by the babel macro when mixing<div tw="..." />
usage withtw.div``
.This happens when using:
Error:
To reproduce:
Maybe this is related to https://github.com/ben-rogerson/twin.macro/issues/192?
Importing in Typescript errors ts(2614)
I finally started trying out twin (you recommended it on reddit a few weeks ago!) having issues starting up in typescript however.
I followed the installation guide for CRA, I've made two code sandboxes setting up: Plain JS (Working): https://codesandbox.io/s/lucid-kapitsa-cjy3y?file=/src/App.js TS (Errors): https://codesandbox.io/s/busy-moser-yxl7g?file=/src/App.tsx
As you can see the JS works fine, and the TS does display the elements styled but there is a TS error for the imports
Module '"../node_modules/twin.macro/types"' has no exported member 'tw'. Did you mean to use 'import tw from "../node_modules/twin.macro/types"' instead?ts(2614)
I'm assuming it can't find the type definitions? I know you say you've got them built in, not sure if I missed something in installation.importing tw in a dynamic route throws module not found error for stitches.config.ts
I'm getting following error when importing and using
tw
in a dynamic route.Found
preferRelative
in webpack's documentation here. But setting it innext.config.js
did not resolve the issue.Add Solid.js support
The
solid-styled-component
package for Solid is a thin wrapper around Goober.This patch adds
preset: solid
, which loads the Goober wrappers and otherwise behaves as Goober would.A small site has been written using this PR, as well as this recently merged Solid PR for
createGlobalStyles
support. If you editpages/index/index.jsx
and comment out lines 30 through 95, you can easily play with the client without any server errors in the console.I'm not sure exactly how you'd like to see these changes implemented and there are currently caveats. At the moment, however, I'd say it's entirely usable as a feature. Whatever you advise is fine.
Here are the current caveats. The following pattern works:
The following will not:
However, the following also works:
Where
Svg
also demonstrates thecss
escape hatch:And most importantly GlobalStyles works:
The only setup needed is
package.json
:and
vite.config.js
: