Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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)
@nrsimonelli
Copy link

nrsimonelli commented Jun 20, 2021

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

lukaskleinschmidt commented Jun 21, 2021

@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

nrsimonelli commented Jun 21, 2021

That is excellent! Thank you for doing this :)

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