Convert your CSS to TailwindCSS 3.x respecting TailwindCSS configuration
๐ VS Code Extension already available ๐
- supports almost all the features (except custom plugins) currently available in TailwindCSS
- the ability to set your own TailwindCSS configuration
- colors are matched regardless of the format used
- rem is converted to px (it is possible to configure the rem size)
- non-convertible CSS declarations are left as CSS
- ambiguities when using css variables are resolved automatically
npm install css-to-tailwindcssimport { TailwindConverter } from 'css-to-tailwindcss';
const converter = new TailwindConverter({
remInPx: 16, // set null if you don't want to convert rem to pixels
postCSSPlugins: [require('postcss-nested')], // add any postcss plugins to this array
tailwindConfig: {
// your tailwind config here
content: [],
theme: {
extend: {
colors: {
'custom-color': {
100: '#123456',
200: 'hsla(210, 100%, 51.0%, 0.016)',
300: '#654321',
gold: 'hsl(41, 28.3%, 79.8%)',
marine: 'rgb(4, 55, 242, 0.75)',
},
},
screens: {
'custom-screen': { min: '768px', max: '1024px' },
},
},
supports: {
grid: 'display: grid',
flex: 'display: flex',
},
},
},
});
const inputCSS = `
:root {
--some-color: #090909;
}
.foo {
padding: 0.875em 256px;
margin-left: 16px;
text-align: center;
font-size: 12px;
transition: color, background-color, border-color, text-decoration-color, fill,
stroke 200ms cubic-bezier(0, 0, 0.2, 1);
animation-delay: 200ms;
&:hover {
filter: blur(4px) brightness(0.5) sepia(100%) contrast(1) hue-rotate(30deg)
invert(0) opacity(0.05) saturate(1.5);
color: hsl(41, 28.3%, 79.8%);
font-size: 1.25rem;
}
&[aria-disabled="true"] {
width: 25%;
color: var(--some-color);
font-size: 1em;
}
@media screen and (min-width: 768px) {
top: auto;
bottom: auto;
left: 25%;
right: 25%;
}
@media (min-width: 768px) and (max-width: 1024px) {
min-width: 100%;
margin-right: -24px;
}
@supports (display: grid) {
display: grid;
grid-column: span 1 / span 1;
}
}
.foo.bar {
padding: 0.875rem 256px 15%;
transform: translateX(12px) translateY(-0.5em) skew(1deg, 3deg)
scale(-0.75, 1.05) rotate(-0.25turn);
&::after {
content: "*";
animation: spin 1s linear infinite;
}
}
`;
converter.convertCSS(inputCSS).then(({ convertedRoot, nodes }) => {
console.log(convertedRoot.toString());
console.log(nodes);
});Console output
convertedRoot.toString():
:root {
--some-color: #090909;
}
.foo {
@apply text-center text-xs ml-4 px-64 py-[0.875em] hover:text-custom-color-gold hover:text-xl aria-disabled:w-3/12 aria-disabled:text-[color:var(--some-color)] aria-disabled:text-[1em] md:inset-x-1/4 md:inset-y-auto custom-screen:min-w-full custom-screen:-mr-6 supports-grid:grid supports-grid:col-span-1;
transition: color, background-color, border-color, text-decoration-color, fill,
stroke 200ms cubic-bezier(0, 0, 0.2, 1);
animation-delay: 200ms;
}
.foo:hover {
filter: blur(4px) brightness(0.5) sepia(100%) contrast(1) hue-rotate(30deg)
invert(0) opacity(0.05) saturate(1.5);
}
.foo.bar {
@apply pt-3.5 pb-[15%] px-64 after:content-["*"] after:animate-spin;
transform: translateX(12px) translateY(-0.5em) skew(1deg, 3deg)
scale(-0.75, 1.05) rotate(-0.25turn);
}Declarations that can't be expressed with utilities without changing the result stay as CSS:
a list of transitions with different durations, filter with opacity() or with functions in an order
different from the one Tailwind applies them in, skew() with two angles, etc.
nodes:
[
{
rule: {
selector: '.foo',
// ...
},
tailwindClasses: [
'text-center',
'text-xs',
'ml-4',
'px-64',
'py-[0.875em]',
'hover:text-custom-color-gold',
'hover:text-xl',
'aria-disabled:w-3/12',
'aria-disabled:text-[color:var(--some-color)]',
'aria-disabled:text-[1em]',
'md:inset-x-1/4',
'md:inset-y-auto',
'custom-screen:min-w-full',
'custom-screen:-mr-6',
'supports-grid:grid',
'supports-grid:col-span-1',
],
},
{
rule: {
selector: '.foo.bar',
// ...
},
tailwindClasses: [
'pt-3.5',
'pb-[15%]',
'px-64',
'after:content-["*"]',
'after:animate-spin',
],
},
];convertedRoot is the whole converted stylesheet, and nodes lists the rules of convertedRoot that got an @apply:
rule is the rule itself and tailwindClasses are the classes of its @apply. Declarations left as CSS are
only in convertedRoot.
Adjacent rules with the same selector are merged if they contain only declarations, have no variants and don't set
the same properties. A rule with variants (e.g. .foo:hover or .foo in a media query)
is merged into the preceding .foo rule if this doesn't change the result. Otherwise a new .foo rule is created
in its place, so a selector may occur in nodes several times, e.g. .foo:hover { color: red } .foo { display: block }
becomes .foo { @apply hover:text-[red] } .foo { @apply block }.
| Option | Type | Default | Description |
|---|---|---|---|
| remInPx | number | null |
null |
rem in px unit. Set null if you don't want to convert rem to pixels |
| arbitraryPropertiesIsEnabled | boolean |
false |
defines whether non-convertible properties should be converted as "arbitrary properties" |
| tailwindConfig | Config |
{ content: [] } |
Set your tailwind config here |
| postCSSPlugins | AcceptedPlugin[] |
[] | Array of acceptable postcss plugins |
| arbitraryVariants | boolean |
false |
converts @media/@supports that don't match the theme to arbitrary variants, e.g. [@media_(max-width:_767px)]: |
| strict | boolean |
false |
avoids utilities that don't reproduce the source declaration exactly: uses exact arbitrary values instead (e.g. text-sm also sets line-height) or leaves the declaration as CSS (e.g. transform functions) |
- Utilities are added with
@applyto the rule they came from. Variants (:hover,::before,[aria-*],[data-*],@media,@supports) are moved to the preceding rule with the base selector only if this can't change which declaration wins; otherwise a new rule with the base selector is created in place, or the utilities are added to the rule itself without variants. before:andafter:variants are used only together with acontent-*utility for the same pseudo-element, because they overridecontentof the source CSS.- A declaration is converted entirely or not at all. Declarations following an unconverted declaration of the same property are not converted, fallbacks (the same property with different values) are kept as CSS.
- Utilities overridden by later declarations of the same rule are dropped, e.g.
margin-top: 16px; margin: 8pxbecomesm-2. - Rules inside
@keyframes,@font-face,@pageand other non-style at-rules are never converted, as well as rules inside native cascade layers (@layerother thanbase,componentsandutilities): utilities don't work there. ::markerand::selectionare not converted to variants, since Tailwind'smarker:andselection:also style the descendants.screen and (...)is converted as(...): thescreenmedia type is dropped.- Tailwind has no per-side border styles. A side shorthand is converted only with the
solidstyle (e.g.border-top: 1px solidbecomesborder-t-current border-t border-solid), which setsborder-style: solidon all sides, as the Tailwind preflight does. Other styles andstrictleave the declaration as CSS. - Unless
strictis enabled, invisible borders are converted idiomatically:border: nonebecomesborder-none,border: none redbecomesborder-none border-[red]andborder-right: nonebecomesborder-r-0, which don't reset the other parts of the border. They are left as CSS if the following declarations of the rule set these parts. - Shorthands reset their omitted parts, so
border: solid redbecomesborder-[medium] border-solid border-[red].transitiondoesn't resettransition-delayunlessstrictis enabled, and Tailwind'stransition-*utilities set a default duration and timing function (e.g.transition: allbecomestransition-all, which animates for 150ms).text-decoration: underlinebecomesunderline, which doesn't reset the style and the color of the line. - Important declarations are converted like with
strict, since the side effects of important utilities would override the other declarations of the rule (e.g.font-size: 14px !importantbecomes!text-[length:14px]). Tailwind keeps the last of duplicate declarations regardless of!important, so an important declaration followed by a remaining declaration of the same property is left as CSS. animate-*utilities add Tailwind's keyframes for the animations of the theme (spin,ping,pulse,bounce), soanimationusing these names is left as CSS if the file defines keyframes with the same name or the Tailwind config has a prefix.transform,filterandbackdrop-filterfunctions are converted to Tailwind utilities, which compose with the functions set by other rules (e.g.rotate-45 hover:translate-x-1keeps the rotation on hover). The same goes fortouch-action,font-variant-numericandscroll-snap-typekeywords (e.g.touch-pan-x hover:touch-pan-ygivespan-x pan-yon hover). Withstrictthese declarations are left as CSS, except for the keywords that don't compose (e.g.touch-action: none).@media (prefers-color-scheme: dark)is converted todark:only withdarkMode: 'media'(default).- Only TailwindCSS 3.x is supported.
The protected methods of TailwindConverter can be overridden:
convertDeclarationToClassesconverts a declaration to classes. Classes that the converter itself doesn't return for the declaration are assumed to set only the declared property;convertSelectorToVariant,convertMediaParamsToVariantsandconvertSupportsParamsToVariantconvert a part of a selector,@mediaor@supportsto variants;parseSelectorsplits a selector into the base selector and the class prefix of its variants. Overriding it converts the variants of a selector as a whole, like 1.0 did;- overriding the deprecated
convertRuleormakeTailwindNodeswitches to the placement of 1.0: utilities are added to the first rule with the same selector, regardless of the cascade.
The package root is the public API. Modules under lib/core/ and exports marked @internal may change in any release.
