Skip to content

Instantly share code, notes, and snippets.

@nicooprat
Last active March 1, 2024 07:36
Show Gist options
  • Save nicooprat/2753f44875d2894183a8d588d6e411c0 to your computer and use it in GitHub Desktop.
Save nicooprat/2753f44875d2894183a8d588d6e411c0 to your computer and use it in GitHub Desktop.
Sharing Tailwind config with SASS (Tailwind beta & Tailwind 1.0)
// Setting variables like this wouldn't be possible because SASS would
// get through this file before Tailwind does (because it's PostCSS)
$--color-primary: theme('colors.blue');
$--font-serif: theme('fontFamily.serif');
body {
color: rgba($--color-primary, .5);
font-family: $font-serif;
}
// It can also be useful to customize some plugins using SASS,
// like ElementUI: https://github.com/ElemeFE/element/blob/dev/packages/theme-chalk/src/common/var.scss
$--color-white: theme('colors.white');
$--color-black: theme('colors.black');
// https://raw.githubusercontent.com/tailwindcss/tailwindcss/master/stubs/defaultConfig.stub.js
module.exports = {
prefix: '',
important: false,
separator: ':',
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
colors: {
transparent: 'transparent',
black: '#000',
white: '#fff',
gray: {
100: '#f7fafc',
200: '#edf2f7',
300: '#e2e8f0',
400: '#cbd5e0',
500: '#a0aec0',
600: '#718096',
700: '#4a5568',
800: '#2d3748',
900: '#1a202c',
},
red: {
100: '#fff5f5',
200: '#fed7d7',
300: '#feb2b2',
400: '#fc8181',
500: '#f56565',
600: '#e53e3e',
700: '#c53030',
800: '#9b2c2c',
900: '#742a2a',
},
orange: {
100: '#fffaf0',
200: '#feebc8',
300: '#fbd38d',
400: '#f6ad55',
500: '#ed8936',
600: '#dd6b20',
700: '#c05621',
800: '#9c4221',
900: '#7b341e',
},
yellow: {
100: '#fffff0',
200: '#fefcbf',
300: '#faf089',
400: '#f6e05e',
500: '#ecc94b',
600: '#d69e2e',
700: '#b7791f',
800: '#975a16',
900: '#744210',
},
green: {
100: '#f0fff4',
200: '#c6f6d5',
300: '#9ae6b4',
400: '#68d391',
500: '#48bb78',
600: '#38a169',
700: '#2f855a',
800: '#276749',
900: '#22543d',
},
teal: {
100: '#e6fffa',
200: '#b2f5ea',
300: '#81e6d9',
400: '#4fd1c5',
500: '#38b2ac',
600: '#319795',
700: '#2c7a7b',
800: '#285e61',
900: '#234e52',
},
blue: {
100: '#ebf8ff',
200: '#bee3f8',
300: '#90cdf4',
400: '#63b3ed',
500: '#4299e1',
600: '#3182ce',
700: '#2b6cb0',
800: '#2c5282',
900: '#2a4365',
},
indigo: {
100: '#ebf4ff',
200: '#c3dafe',
300: '#a3bffa',
400: '#7f9cf5',
500: '#667eea',
600: '#5a67d8',
700: '#4c51bf',
800: '#434190',
900: '#3c366b',
},
purple: {
100: '#faf5ff',
200: '#e9d8fd',
300: '#d6bcfa',
400: '#b794f4',
500: '#9f7aea',
600: '#805ad5',
700: '#6b46c1',
800: '#553c9a',
900: '#44337a',
},
pink: {
100: '#fff5f7',
200: '#fed7e2',
300: '#fbb6ce',
400: '#f687b3',
500: '#ed64a6',
600: '#d53f8c',
700: '#b83280',
800: '#97266d',
900: '#702459',
},
},
spacing: {
px: '1px',
'0': '0',
'1': '0.25rem',
'2': '0.5rem',
'3': '0.75rem',
'4': '1rem',
'5': '1.25rem',
'6': '1.5rem',
'8': '2rem',
'10': '2.5rem',
'12': '3rem',
'16': '4rem',
'20': '5rem',
'24': '6rem',
'32': '8rem',
'40': '10rem',
'48': '12rem',
'56': '14rem',
'64': '16rem',
},
backgroundColor: theme => theme('colors'),
backgroundPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain',
},
borderColor: theme => ({
...theme('colors'),
default: theme('colors.gray.300', 'currentColor'),
}),
borderRadius: {
none: '0',
sm: '0.125rem',
default: '0.25rem',
lg: '0.5rem',
full: '9999px',
},
borderWidth: {
default: '1px',
'0': '0',
'2': '2px',
'4': '4px',
'8': '8px',
},
boxShadow: {
default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
none: 'none',
},
container: {},
cursor: {
auto: 'auto',
default: 'default',
pointer: 'pointer',
wait: 'wait',
text: 'text',
move: 'move',
'not-allowed': 'not-allowed',
},
fill: {
current: 'currentColor',
},
flex: {
'1': '1 1 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none',
},
flexGrow: {
'0': '0',
default: '1',
},
flexShrink: {
'0': '0',
default: '1',
},
fontFamily: {
sans: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
serif: [
'Georgia',
'Cambria',
'"Times New Roman"',
'Times',
'serif',
],
mono: [
'Menlo',
'Monaco',
'Consolas',
'"Liberation Mono"',
'"Courier New"',
'monospace',
],
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '4rem',
},
fontWeight: {
hairline: '100',
thin: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
height: theme => ({
auto: 'auto',
...theme('spacing'),
full: '100%',
screen: '100vh',
}),
inset: {
'0': '0',
auto: 'auto',
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
}),
maxHeight: {
full: '100%',
screen: '100vh',
},
maxWidth: {
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
full: '100%',
},
minHeight: {
'0': '0',
full: '100%',
screen: '100vh',
},
minWidth: {
'0': '0',
full: '100%',
},
objectPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
opacity: {
'0': '0',
'25': '0.25',
'50': '0.5',
'75': '0.75',
'100': '1',
},
order: {
first: '-9999',
last: '9999',
none: '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'10': '10',
'11': '11',
'12': '12',
},
padding: theme => theme('spacing'),
stroke: {
current: 'currentColor',
},
textColor: theme => theme('colors'),
width: theme => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.33333%',
'2/3': '66.66667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.66667%',
'2/6': '33.33333%',
'3/6': '50%',
'4/6': '66.66667%',
'5/6': '83.33333%',
'1/12': '8.33333%',
'2/12': '16.66667%',
'3/12': '25%',
'4/12': '33.33333%',
'5/12': '41.66667%',
'6/12': '50%',
'7/12': '58.33333%',
'8/12': '66.66667%',
'9/12': '75%',
'10/12': '83.33333%',
'11/12': '91.66667%',
full: '100%',
screen: '100vw',
}),
zIndex: {
auto: 'auto',
'0': '0',
'10': '10',
'20': '20',
'30': '30',
'40': '40',
'50': '50',
},
},
variants: {
alignContent: ['responsive'],
alignItems: ['responsive'],
alignSelf: ['responsive'],
appearance: ['responsive'],
backgroundAttachment: ['responsive'],
backgroundColor: ['responsive', 'hover', 'focus'],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
borderCollapse: ['responsive'],
borderColor: ['responsive', 'hover', 'focus'],
borderRadius: ['responsive'],
borderStyle: ['responsive'],
borderWidth: ['responsive'],
boxShadow: ['responsive', 'hover', 'focus'],
cursor: ['responsive'],
display: ['responsive'],
fill: ['responsive'],
flex: ['responsive'],
flexDirection: ['responsive'],
flexGrow: ['responsive'],
flexShrink: ['responsive'],
flexWrap: ['responsive'],
float: ['responsive'],
fontFamily: ['responsive'],
fontSize: ['responsive'],
fontSmoothing: ['responsive'],
fontStyle: ['responsive'],
fontWeight: ['responsive', 'hover', 'focus'],
height: ['responsive'],
inset: ['responsive'],
justifyContent: ['responsive'],
letterSpacing: ['responsive'],
lineHeight: ['responsive'],
listStylePosition: ['responsive'],
listStyleType: ['responsive'],
margin: ['responsive'],
maxHeight: ['responsive'],
maxWidth: ['responsive'],
minHeight: ['responsive'],
minWidth: ['responsive'],
objectFit: ['responsive'],
objectPosition: ['responsive'],
opacity: ['responsive'],
order: ['responsive'],
outline: ['responsive', 'focus'],
overflow: ['responsive'],
padding: ['responsive'],
pointerEvents: ['responsive'],
position: ['responsive'],
resize: ['responsive'],
stroke: ['responsive'],
tableLayout: ['responsive'],
textAlign: ['responsive'],
textColor: ['responsive', 'hover', 'focus'],
textDecoration: ['responsive', 'hover', 'focus'],
textTransform: ['responsive'],
userSelect: ['responsive'],
verticalAlign: ['responsive'],
visibility: ['responsive'],
whitespace: ['responsive'],
width: ['responsive'],
wordBreak: ['responsive'],
zIndex: ['responsive'],
},
corePlugins: {},
plugins: [],
}
const sass = require('node-sass');
const sassUtils = require('node-sass-utils')(sass);
const { theme } = require('./tailwind.config.js');
const hexToRGBA = (hex) => {
if (!/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
return null;
}
let hexColor = hex.substring(1).split('');
if (hexColor.length === 3) {
hexColor = [hexColor[0], hexColor[0], hexColor[1], hexColor[1], hexColor[2], hexColor[2]];
}
return Number(`0xff${hexColor.join('')}`);
};
const convertStringToSassDimension = (result) => {
// Only attempt to convert strings
if (sassUtils.typeOf(result) !== 'string') {
return result;
}
const cssUnits = ['rem', 'em', 'vh', 'vw', 'vmin', 'vmax', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch'];
const value = result.match(/[.0-9]+/g);
const unit = result.match(/[a-zA-Z%]+/g);
// If the string has a unit
if (cssUnits.indexOf(unit) !== -1) {
return new sassUtils.SassDimension(parseInt(value, 10), unit);
}
// Else if the string is a hex color string, make sure we return a sass color
// to avoid errors when using darken, lighten, etc.
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(result)) {
const rgba = hexToRGBA(result);
return sass.types.Color(rgba);
}
return result;
};
// sass-loader function to return specific theme item
const getConfigItem = (keys) => {
const itemValue = keys
? keys.getValue().split('.').reduce((object, item) => object[item] || {}, theme)
: theme;
let returnValue;
if (itemValue) {
if (sassUtils.typeOf(itemValue) === 'string') {
returnValue = convertStringToSassDimension(itemValue);
} else if (sassUtils.typeOf(itemValue) === 'array') {
returnValue = convertStringToSassDimension(itemValue.join(', '));
} else if (sassUtils.typeOf(itemValue) === 'object') {
returnValue = Object.keys(itemValue).reduce((object, item) => {
let itemKeys = `${keys ? `${sassUtils.castToJs(keys)}.` : ''}${item}`;
itemKeys = sassUtils.castToSass(itemKeys);
return {
...object,
[item]: getConfigItem(itemKeys),
};
}, {});
}
}
return sassUtils.castToSass(returnValue);
};
// sass-loader function to return the entire theme
const getConfig = () => getConfigItem();
module.exports = {
getConfig,
getConfigItem,
};
const tailwindSassLoader = require('./tailwind.sass')
module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: (loader) => [
tailwindcss('./tailwind.config.js'),
autoprefixer(),
],
},
sass: {
functions: {
'theme($keys)': tailwindSassLoader.getConfigItem,
},
}
},
}
}
@nicooprat
Copy link
Author

Original issue on Tailwind repo

Here's I'm using Vue-CLI to configure Webpack, but only this line is important.

The SASS loader is based on the work of @jlpospisil in this file.

@nicooprat
Copy link
Author

Just made it work with Tailwind 1.0. See revisions for older versions compatible with beta.

@aaronjpitts
Copy link

This is such a handy script. Thank you very much :)

@Krlinhos
Copy link

Krlinhos commented Aug 31, 2021

Hello,

It's a great idea.

But I'm using a laravelMix with webpack.mix.js and webpack.config.js. I dont know where should put this config:
sass: { functions: { 'theme($keys)': tailwindSassLoader.getConfigItem, }, }

my webpack.mix looks like:
`
const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');

mix.js('resources/js/app.js', 'public/js')
.vue()
.sass('resources/sass/app.sass', 'public/css')
.options({
processCssUrls: false,
postCss: [
tailwindcss('./tailwind.config.js')
]
})
.webpackConfig(require('./webpack.config'));

if (mix.inProduction()) {
mix.version();
}`

and webpack.config.js:
`
const path = require('path');

module.exports = {
resolve: {
alias: {
'@': path.resolve('resources/js'),
},
}
};
`

Can you help me? thanks! :)

@nicooprat
Copy link
Author

I never used Mix, but it looks like it should be added there:

const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');

mix.js('resources/js/app.js', 'public/js')
.vue()
.sass('resources/sass/app.sass', 'public/css')
.options({
+functions: { 'theme($keys)': tailwindSassLoader.getConfigItem },
processCssUrls: false,
postCss: [
tailwindcss('./tailwind.config.js')
]
})
.webpackConfig(require('./webpack.config'));

if (mix.inProduction()) {
mix.version();
}

@Krlinhos
Copy link

Krlinhos commented Sep 3, 2021

Hello!

Yeah! thanks its works :)

@brunofamiliar
Copy link

Hi! Thanks for sharing.

This works for nuxt.js. Just import tailwindSassLoader into nuxt.config.js and the following configuration:

image

@manichandra
Copy link

Thanks for sharing. any idea on how to use in Angular ?

@nicooprat
Copy link
Author

Sorry, no idea!

@yainspan
Copy link

A couple of things I want to add:

  1. When using with Vite, the config is similar:
import { getConfigItem } from "./tailwind.sass"; 
// or const { getConfigItem }  = require('./tailwind.sass.js');

export default defineConfig({
    ...
    css: {
        preprocessorOptions: {
            scss: {
                functions: {
                    "theme($keys)": getConfigItem,
                },
            },
        },
    },
});
  1. Later versions of node-sass (I'm on 9.0.0) have sass.types.Color deprecated and it doesn't seem to work. To fix that, replace the hexToRGBA function with this:

const hexToRGBA = (hex) => {
    const red = parseInt(hex.slice(1, 3), 16),
        green = parseInt(hex.slice(3, 5), 16),
        blue = parseInt(hex.slice(5, 7), 16);

    return {
        red,
        green,
        blue,
    };
};

https://stackoverflow.com/a/28056903/4633197

and then change the part of convertStringToSassDimension that handles the hex values to this:

if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(result)) {
    const rgba = hexToRGBA(result);
    return new SassColor(rgba);
}

The SassColor constructor only takes an object of red, blue, and green, and optional alpha: https://sass-lang.com/documentation/js-api/classes/sasscolor/

@nicooprat
Copy link
Author

Thanks for sharing 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment