Skip to content

Instantly share code, notes, and snippets.

@lukaskleinschmidt
Last active September 11, 2023 14:50
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save lukaskleinschmidt/f4c10d15d013fec8f8b8a341d9ade859 to your computer and use it in GitHub Desktop.
Save lukaskleinschmidt/f4c10d15d013fec8f8b8a341d9ade859 to your computer and use it in GitHub Desktop.
Utility class generator like tailwindcss but in pure Sass.
@use 'sass:map';
@use 'variants' as * with (
$breakpoints: (
'small': 640px,
'medium': 768px,
'large': 1024px,
'wide': 1280px,
)
);
// ASCII Art Generator
// http://www.patorjk.com/software/taag/#p=display&v=0&f=ANSI%20Shadow&t=variant
// ██████╗ █████╗ ███╗ ██╗ █████╗ ███╗ ██╗ █████╗
// ██╔══██╗██╔══██╗████╗ ██║██╔══██╗████╗ ██║██╔══██╗
// ██████╔╝███████║██╔██╗ ██║███████║██╔██╗ ██║███████║
// ██╔══██╗██╔══██║██║╚██╗██║██╔══██║██║╚██╗██║██╔══██║
// ██████╔╝██║ ██║██║ ╚████║██║ ██║██║ ╚████║██║ ██║
// ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
.banana {
@include variants('responsive' 'hover' 'group-hover') {
color: #FFE135;
}
}
// ████████╗███████╗██╗ ██╗████████╗
// ╚══██╔══╝██╔════╝╚██╗██╔╝╚══██╔══╝
// ██║ █████╗ ╚███╔╝ ██║
// ██║ ██╔══╝ ██╔██╗ ██║
// ██║ ███████╗██╔╝ ██╗ ██║
// ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
$text: (
'left': left,
'right': right,
'center': center,
'justify': justify,
);
.text {
@include options($text, 'responsive') using ($value) {
text-align: $value;
}
}
// ███████╗██████╗ █████╗ ██████╗██╗███╗ ██╗ ██████╗
// ██╔════╝██╔══██╗██╔══██╗██╔════╝██║████╗ ██║██╔════╝
// ███████╗██████╔╝███████║██║ ██║██╔██╗ ██║██║ ███╗
// ╚════██║██╔═══╝ ██╔══██║██║ ██║██║╚██╗██║██║ ██║
// ███████║██║ ██║ ██║╚██████╗██║██║ ╚████║╚██████╔╝
// ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
$spacing: (
'px': 1px,
'0': 0,
'1': .25rem,
'2': .5rem,
'3': .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,
);
// Add auto to the positive margin classes
$margin: map.merge((
'auto': auto
), $spacing);
@include variants('responsive') using ($props...) {
// ██████╗ █████╗ ██████╗ ██████╗ ██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║██╔════╝
// ██████╔╝███████║██║ ██║██║ ██║██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║██║ ██║██║ ██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║██████╔╝██████╔╝██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝
.p {
@include options($spacing, $props...) using ($value) {
padding: $value;
}
}
.px {
@include options($spacing, $props...) using ($value) {
padding-right: $value;
padding-left: $value;
}
}
.py {
@include options($spacing, $props...) using ($value) {
padding-bottom: $value;
padding-top: $value;
}
}
.pt {
@include options($spacing, $props...) using ($value) {
padding-top: $value;
}
}
.pr {
@include options($spacing, $props...) using ($value) {
padding-right: $value;
}
}
.pb {
@include options($spacing, $props...) using ($value) {
padding-bottom: $value;
}
}
.pl {
@include options($spacing, $props...) using ($value) {
padding-left: $value;
}
}
// ███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗███╗ ██╗
// ████╗ ████║██╔══██╗██╔══██╗██╔════╝ ██║████╗ ██║
// ██╔████╔██║███████║██████╔╝██║ ███╗██║██╔██╗ ██║
// ██║╚██╔╝██║██╔══██║██╔══██╗██║ ██║██║██║╚██╗██║
// ██║ ╚═╝ ██║██║ ██║██║ ██║╚██████╔╝██║██║ ╚████║
// ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝
.m {
@include options($margin, $props...) using ($value) {
margin: $value;
}
}
.mx {
@include options($margin, $props...) using ($value) {
margin-right: $value;
margin-left: $value;
}
}
.my {
@include options($margin, $props...) using ($value) {
margin-bottom: $value;
margin-top: $value;
}
}
.mt {
@include options($margin, $props...) using ($value) {
margin-top: $value;
}
}
.mr {
@include options($margin, $props...) using ($value) {
margin-right: $value;
}
}
.mb {
@include options($margin, $props...) using ($value) {
margin-bottom: $value;
}
}
.ml {
@include options($margin, $props...) using ($value) {
margin-left: $value;
}
}
// ███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗███╗ ██╗
// ████╗ ████║██╔══██╗██╔══██╗██╔════╝ ██║████╗ ██║
// █████╗██╔████╔██║███████║██████╔╝██║ ███╗██║██╔██╗ ██║
// ╚════╝██║╚██╔╝██║██╔══██║██╔══██╗██║ ██║██║██║╚██╗██║
// ██║ ╚═╝ ██║██║ ██║██║ ██║╚██████╔╝██║██║ ╚████║
// ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝
.-m {
@include options($spacing, $props...) using ($value) {
margin: -$value;
}
}
.-mx {
@include options($spacing, $props...) using ($value) {
margin-right: -$value;
margin-left: -$value;
}
}
.-my {
@include options($spacing, $props...) using ($value) {
margin-bottom: -$value;
margin-top: -$value;
}
}
.-mt {
@include options($spacing, $props...) using ($value) {
margin-top: -$value;
}
}
.-mr {
@include options($spacing, $props...) using ($value) {
margin-right: -$value;
}
}
.-mb {
@include options($spacing, $props...) using ($value) {
margin-bottom: -$value;
}
}
.-ml {
@include options($spacing, $props...) using ($value) {
margin-left: -$value;
}
}
}
// ██╗ █████╗ ██╗ ██╗ ██████╗ ██╗ ██╗████████╗
// ██║ ██╔══██╗╚██╗ ██╔╝██╔═══██╗██║ ██║╚══██╔══╝
// ██║ ███████║ ╚████╔╝ ██║ ██║██║ ██║ ██║
// ██║ ██╔══██║ ╚██╔╝ ██║ ██║██║ ██║ ██║
// ███████╗██║ ██║ ██║ ╚██████╔╝╚██████╔╝ ██║
// ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝
$display: (
'block': block,
'inline-block': inline-block,
'inline': inline,
'flex': flex,
'inline-flex': inline-flex,
'grid': grid,
'inline-grid': inline-grid,
'hidden': none,
) !default;
@include options($display, 'responsive') using ($value) {
display: $value;
}
@use 'sass:list'
@use 'sass:meta'
@use 'sass:selector'
@use 'sass:string'
/// List of available breakpoints.
///
/// @type Map
$breakpoints: () !default
/// The position of the breakpoint to the selector.
///
/// @type String
$breakpoint-position: 'before' !default
/// The string used to attach breakpoints.
///
/// @type String
$breakpoint-glue: '\\:' !default
/// The position of the variant to the selector.
///
/// @type String
$variant-position: 'before' !default
/// The string used to attach variants.
///
/// @type String
$variant-glue: '\\:' !default
/// The string used to attach options.
///
/// @type String
$option-glue: '-' !default
/// Unset a given value from a list.
///
/// @param {List} $list - The string that will be trimmed.
/// @param {String} $value - Value that you want to be removed.
/// @return {List} Returns the filtered list.
@function _remove($list, $value)
$result: ()
@for $i from 1 through list.length($list)
@if list.nth($list, $i) != $value
$result: list.append($result, list.nth($list, $i))
@return $result
/// Generates the selector depending on the current variant and breakpoint.
///
/// @param {String} $variant [null]
/// @param {String} $breakpoint [null]
/// @return {List} Returns the updated selector.
@function _selector($variant: null, $breakpoint: null)
$selectors: ()
@each $selector in &
$selector: list.nth($selector, 1)
@if meta.type-of($selector) == 'string'
$selector: string.slice($selector, 2)
// Add the variant to the current selector
@if $variant
@if $variant-position == 'after'
$selector: '#{$selector}#{$variant-glue}#{$variant}'
@else
$selector: '#{$variant}#{$variant-glue}#{$selector}'
// Add the breakpoint to the current selector
@if $breakpoint
@if $breakpoint-position == 'after'
$selector: '#{$selector}#{$breakpoint-glue}#{$breakpoint}'
@else
$selector: '#{$breakpoint}#{$breakpoint-glue}#{$selector}'
// Convert back to a class
@if $selector
$selectors: list.append($selectors, '.#{$selector}', comma)
@return $selectors
/// Appends a pseudo-class to a selector.
///
/// @param {String} $selector
/// @param {String} $pseudo-class [null]
/// @return {String} Returns the updated selector.
@function _pseudo-class($selector, $pseudo-class: null)
@if $pseudo-class
$selector: selector.nest($selector, '&:#{$pseudo-class}')
@return $selector
/// Generates the css for a variant and breakpoint.
///
/// @param {String} $variant
/// @param {String} $breakpoint
@mixin _variant($variant, $breakpoint)
$pseudo-class: $variant
$group: null
// Handle group variants
@if meta.type-of($variant) == 'string' and string.index($variant, 'group-')
$pseudo-class: string.slice($pseudo-class, 7)
$group: '.group'
// Get the selector depending on the current variant and breakpoint
$selector: _selector($variant, $breakpoint)
@if $group
#{_pseudo-class($group, $pseudo-class)}
#{$selector}
@content
@else
#{_pseudo-class($selector, $pseudo-class)}
@content
/// Distributes the input to the correct output.
///
/// @param {List} $variants
/// @param {String} $breakpoint
@mixin _io($variants, $breakpoint)
@if &
@include _variant(null, $breakpoint)
@content
@each $variant in $variants
@include _variant($variant, $breakpoint)
@content
@else
@content($variants, $breakpoint)
/// Generates the css for the given variants.
///
/// @param {List} $variants [()]
/// @param {String} $breakpoint [null]
@mixin variants($variants: (), $breakpoint: null)
// Check for the responsive variant
$responsive: list.index($variants, 'responsive')
@if $responsive
$variants: _remove($variants, 'responsive')
@at-root
@include _io($variants, $breakpoint) using ($data...)
@content($data...)
@if $responsive
@each $breakpoint, $value in $breakpoints
@media screen and (min-width: #{$value})
@include _io($variants, $breakpoint) using ($data...)
@content($data...)
/// Responsive alias.
///
/// @see {Mixin} variants
/// @param {List} $variants [()]
@mixin responsive($variants: ())
// Make responsive by default
$variants: list.append($variants, 'responsive', space)
@include variants($variants) using ($data...)
@content($data...)
/// Helper for creating option based modifiers or classes.
///
/// @param {Map} $options
/// @param {List} $variants [()]
/// @param {List} $breakpoint [null]
@mixin options($options, $variants: (), $breakpoint: null)
@if &
// Render options as a class modifier
@each $key, $value in $options
&#{$option-glue}#{$key}
@include variants($variants, $breakpoint)
@content($value)
@else
// Render options as seperate classes
@include variants($variants, $breakpoint) using ($props...)
@each $key, $value in $options
.#{$key}
@include variants($props...)
@content($value)
@bilalqtech
Copy link

bilalqtech commented Apr 26, 2020

Hi mate,
There's typo somewhere, either in filename varaints.scss or where you import it in your app.scss
I'm guessing it's filename because right spelling is variants.scss.

@lukaskleinschmidt
Copy link
Author

Good catch @bilalqtech

@devhoussam
Copy link

How can I enable comma "," inside @include variants('responsive' 'hover' 'group-hover') {}

like this : @include variants('responsive', 'hover', 'group-hover') {}

@cssninjaStudio
Copy link

@lukaskleinschmidt The variants.sass throws an error in my compiler. It seems it is not valid:

Error in plugin "sass"
Message:
    src\scss\utilities\_variants.sass
Error: Invalid CSS after "... 1 through list": expected "{", was ".length($list) {"
        on line 44 of src/scss/utilities/_variants.sass
        from line 14 of src/scss/main.scss
>>   @for $i from 1 through list.length($list) {

@lukaskleinschmidt
Copy link
Author

@devhoussam Your desired syntax is not possible.
You should be able to use it like this though @include variants(('responsive', 'hover', 'group-hover')) {}

@cssninjaStudio what version of sass are you using?

@cssninjaStudio
Copy link

cssninjaStudio commented Dec 18, 2020

@lukaskleinschmidt using gulp-sass 4.1.0. VS Code doesn't validate the file as well.

@lukaskleinschmidt
Copy link
Author

I think gulp-sass uses node-sass out of the box. As far as I know LibSass (node-sass) still has not catched up to Dart Sass yet.
You need to change the sass compiler in your gulp setup to use the Dart Sass (sass) implementation. This should fix the issue.

var gulp = require('gulp');
var sass = require('gulp-sass');
 
sass.compiler = require('sass');

@cssninjaStudio
Copy link

@lukaskleinschmidt thanks for the answer, I tried that but it does not work. I have the latest gulp-sass and here is what I have in the gulpfile

const { src, dest, task, watch, series, parallel } = require('gulp');
const del = require('del');
const options = require("./config");
const browserSync = require('browser-sync').create();

const sass = require('gulp-sass');
const bourbon = require('node-bourbon').includePaths;
const postcss = require('gulp-postcss');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const imagemin = require('gulp-imagemin');
const cleanCSS = require('gulp-clean-css');
const purgecss = require('gulp-purgecss');
const sourcemaps = require('gulp-sourcemaps');
const autoprefixer = require('gulp-autoprefixer');
const panini = require('panini');

const browserify = require("browserify");
const babelify = require("babelify");
const source = require("vinyl-source-stream");
const nodepath = 'node_modules/';

sass.compiler = require('sass');

When trying to run gulp I get this:

Error: Cannot find module 'sass'

Should I install a package that I don't have? I read dart-sass is bundled with gulp-sass so that should work, shouldn't it?

@lukaskleinschmidt
Copy link
Author

From the error it sems like it is not installed. So I would try to install it manually npm i -D sass

@cssninjaStudio
Copy link

cssninjaStudio commented Dec 18, 2020

@lukas Kleinschmidt Got it working, thanks !

@tony
Copy link

tony commented Jan 30, 2021

@lukaskleinschmidt Thank you!

Can anyone convert the variants.sass to scss?

https://jsonformatter.org/sass-to-scss doesn't do the trick

Is the license on this MIT / ISC? Any of it derived from elsewhere?

@YuukanOO
Copy link

YuukanOO commented Feb 1, 2021

Nice work @lukaskleinschmidt! I really like Tailwind but that JS when SASS could handle it easily and does not make you depend on a complex toolchain + rules such as @extend .text-right will work perfectly.

Is there a SASS library which cover almost every Tailwind stuff out there? Because may be your work can be a great start!

@lukaskleinschmidt
Copy link
Author

@tony, @YuukanOO

Actually it was written in SCSS at the beginning but I switched to Sass because it was a bit easier to read.
I also have a updated version which in the end is now written in SCSS again because working with lists or maps in Sass was just horrible.

In addition I created a repo to give this thing a proper place to live (and MIT License) as it seems that there are actually people using this 🙂
https://github.com/lukaskleinschmidt/snug

It would be super interesting to see how you use this. Or how you would like to use it. Feel free to leave a comment here:
lukaskleinschmidt/snug#1

I created some predefined helpers some time ago. You can have a look at it here and how you can use them here.

@nrsimonelli
Copy link

Hi @lukaskleinschmidt,

Thanks for making this great tool--I have found it really useful so far when spinning up new projects. Currently, I am attempting to create classes that all share the same parent :root.night but am struggling to figure out how to do so with the options mixin.

I have been able to create exactly what I want in terms of utility classes like this ->

.night\:bg { @include options($colors) using ($value) { background: $value; } }

But cant quite figure out how to add a parent selector so that I yield css results like this:

:root.night .night\:bg-blue-100 { background: #f3f4f6; }

and so on..

Have been stumped on this for awhile so thank you in advance

@lukaskleinschmidt
Copy link
Author

@nrsimonelli This is not possible with the current script in this gist.
But since this seems to be a feature that might be useful for others as well, I've integrated it in the updated version

https://github.com/lukaskleinschmidt/snug

$colors: (
  'blue': (
    100: #e5f1fd,
    200: #b0d6f9,
    300: #7bbaf6,
    400: #469ff2,
    500: #1183ee,
    600: #0d66b9,
    700: #094984,
    800: #062c4f,
    900: #020f1a,
  )
);

:root.night {
  .night\:bg {
    @include options($colors) using ($value) {
      background-color: $value;
    }
  }
}

@nrsimonelli
Copy link

That is excellent! Thank you for doing this :)

@devzarghami
Copy link

devzarghami commented May 1, 2023

Hello friends, I developed a simpler version with more customization capabilities
In this version, you can easily customize all the classes
This version has the ability to implement light and dark mode very fully and also supports RTL/LTR structure

https://github.com/devzarghami/scss-class-generator

example usage: https://atiex.uk/en

@astrit
Copy link

astrit commented May 1, 2023

Hello friends, I developed a simpler version with more customization capabilities In this version, you can easily customize all the classes This version has the ability to implement light and dark mode very fully and also supports RTL/LTR structure

https://github.com/devzarghami/scss-class-generator

example usage: https://atiex.uk/en

The url is 404

@devzarghami
Copy link

The link issue has been resolved

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