A style system generator and utility-first CSS framework built entirely in Sass.
This was inspired by similar utility-first frameworks such as Tailwind and Tachyons, but it comes with the power of a CSS preprocessor and zero dependencies other than dart-sass—which is fast and self-contained. As opposed to dealing with a Node build system, an "Electron for CLI's" binary, or the plethora of plugins needed for PostCSS.
- Highly customizable style system
- Resonsive-first & utility-first classes
- Support for dark mode utility classes
- Beautiful presets for color, shadow, scale, and typography
- Stacked pseudo selector classes for precision styling
- Color scale generator for custom colors
- Easily extended with custom Sass
If you don't need any customization and want to start using the utility classes right away, you can link directly to the default build in your HTML:
<link rel="stylesheet" href="https://unpkg.com/[email protected]/index.min.css" />
If you want to customize your build, you'll need to install sass-system into your project using one of these methods:
- Download the latest release
- Install with npm:
npm install sass-system
@use "path/to/sass-system";
That's it! Use --load-path to include the vendor directory for your package management (could be vendor
, node_modules
, etc.), and all the utility classes will now be included in your CSS output.
<div class="m-5 p-5 bg-gray-8 max-w-6 md:max-w-7">
<h1 class="mb-4 pink-5 text-xxl text-heavy">
Welcome to Sass System!
</h1>
<p class="text-sm text-light lead-copy">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
</p>
</div>
If you find yourself frequently reusing utility classes and want to extract common components into custom classes, you can access style system values directly using getters:
@use "path/to/sass-system" as ss;
.my-custom-class {
background: ss.color(gray-8);
padding: ss.space(5);
@include ss.media-up-to(md) {
padding: ss.space(6);
}
}
If you want to customize the default values, you can pass custom configuration using with
. Note that all custom values will override the defaults unless they are configured within the $extend map.
@use "path/to/sass-system" with (
$primary-fonts: (
sans-serif: "Fira Sans",
monospace: "Fira Mono"
),
$extend: (
brand-colors: (
off-black: #202020
)
)
);
- $normalize
- $class-prefix
- $separator
- $dark-mode
- $screens
- $spacing
- $primary-fonts
- $font-stacks
- $font-weights
- $font-sizes
- $font-leading
- $font-tracking
- $text-indent
- $box-shadows
- $colors
- $brand-colors
- $selectors
- $generators
- $extend
specs
type:
boolean
default:true
Whether to import modern-normalize and some other opinionated normalization rules at the top of your stylesheet.
specs
type:
string
default: none
Used to prefix a value to every generated class name. This is turned off by default, so classes will look like .bg-black
. e.g. If set to ss-
, classes will look like .ss-bg-black
.
specs
type:
string
default:\\:
Used to separate base class names from variants such as dark selectors, pseudo selectors, and responsive selectors. Using the default value of an escaped colon will generate classes such as .sm\:dark\:bg-black
, which can be used as <div class="sm:dark:bg-black">
.
specs
type:
string
default:media
Enables dark mode classes using media queries or a parent class. Setting to media
will create classes within @media (prefers-color-scheme: dark)
, and setting to class
will create classes under a parent .dark
class such as .dark .dark\:bg-black
.
specs
type:
map
getter:screen($name)
default:( xs: 640px, sm: 768px, md: 1024px, lg: 1280px, xl: 1792px )
Named screen size values which are used to generate responsive classes within media queries such as .md\:bg-black
. These key names cannot be used for screen sizes due to conflicts with width classes: 0
, px
, full
, screen
, min
, max
, fit
, auto
specs
type:
list
getter:space($index)
default:[ .25rem, .5rem, 1rem, 1.25rem, 1.5rem, 2rem, 2.5rem, 3rem, 4rem, 6rem, 10rem, 18rem ]
Spacing values used for margin and padding. Class names correspond with the list index starting at 1. e.g. .m-1
will correspond to margin: .25rem
, and .p-8
will correspond to padding: 32rem
. These classes are prefixed with .m-*
and .p-*
. See the margin generator and padding generator for more info.
specs
type:
map
default:( serif: null, sans-serif: null, monospace: null )
A primary font can be set for each type of font stack, such as monospace: "Fira Mono"
, which prepends to the default font stack of that same type. This avoids having to redefine entire system font stacks from scratch just to add a custom font.
specs
type:
map
getter:font($name)
default:( serif: [ ui-serif, Georgia, Cambria, "Times New Roman", Times, serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" ], sans-serif: [ ui-sans-serif, system-ui, -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" ], monospace: [ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" ] )
Named list values for font family stacks. Rather than redefining entire font stacks to specify a single custom font, it's recommended to specify a primary font instead. These classes do not have a prefix, so make sure there are no key conflicts with $brand-colors. See the font-family generator for more info.
specs
type:
map
getter:font-weight($name)
default:( thin: 100, extra-light: 200, light: 300, regular: 400, medium: 500, semi-bold: 600, bold: 700, extra-bold: 800, heavy: 900 )
Named values used for font weight. These classes are prefixed with .text-*
, so make sure there are no key conflicts with $font-sizes. See the font-weight generator for more info.
specs
type:
map
getter:font-size($name)
default:( xxs: .75rem, xs: .875rem, sm: 1rem, md: 1.25rem, lg: 1.5rem, xl: 2.25rem, xxl: 3rem, jumbo: 6rem )
Named values used for font size. These classes are prefixed with .text-*
, so make sure there are no key conflicts with $font-weights. See the font-size generator for more info.
specs
type:
map
getter:font-lead($name)
default:( tight: .9em, solid: 1em, title: 1.25em, copy: 1.5em, wide: 2em )
Named values used for font leading (line height). These classes are prefixed with .lead-*
. See the line-height generator for more info.
specs
type:
map
getter:font-track($name)
default:( tight: -.05em, open: .1em, mega: .25em )
Named values used for font tracking (letter spacing). These classes are prefixed with .track-*
. See the letter-spacing generator for more info.
specs
type:
number
default:1em
Value used for the text indentation class. See the text-indent generator for more info.
specs
type:
map
getter:box-shadow($name)
default:( low: (0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)), mid: (0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)), high: (0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)), mega: 0 25px 50px -12px rgb(0 0 0 / 0.25), inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05) )
Named values used for box shadow. These classes are prefixed with .shadow-*
. See the box-shadow generator for more info.
specs
type:
map
getter:color($name-$index)
default:( gray: #adb5bd, cyan: #0dcaf0, teal: #20c997, green: #198754, yellow: #ffc107, orange: #fd7e14, red: #dc3545, pink: #d63384, purple: #6f42c1, indigo: #6610f2, blue: #0d6efd )
Named values for colors that get auto-generated variations. 4 darker variations and 4 lighter variations will be generated for each color, for a total of 9 colors per key. These classes are prefixed with the color name. The base color will exist as .[name]-5
with .[name]-[1-4]
for lighter variations and .[name]-[6-9]
for darker variations. See the color generator and background-color generator for more info.
specs
type:
map
getter:color($name)
default:( black: #000000, white: #ffffff )
Named values for brand colors that don't have auto-generated variations. These classes do not have a prefix, e.g. black
creates a .black
class. Make sure there are no key conflicts with $font-stacks. See the color generator and background-color generator for more info.
specs
type:
map
default:( // pseudo classes hover: ":hover", focus: ":focus", focus-within: ":focus-within", focus-visible: ":focus-visible", active: ":active", visited: ":visited", target: ":target", first: ":first-child", last: ":last-child", only: ":only-child", odd: ":nth-child(odd)", even: ":nth-child(even)", first-of-type: ":first-of-type", last-of-type: ":last-of-type", only-of-type: ":only-of-type", empty: ":empty", disabled: ":disabled", checked: ":checked", indeterminate: ":indeterminate", default: ":default", required: ":required", valid: ":valid", invalid: ":invalid", in-range: ":in-range", out-of-range: ":out-of-range", placeholder-shown: ":placeholder-shown", autofill: ":autofill", read-only: ":read-only", rtl: ":dir(rtl)", ltr: ":dir(ltr)", // pseudo elements before: "::before", after: "::after", first-letter: "::first-letter", first-line: "::first-line", marker: "::marker", selection: "::selection", file: "::file-selector-button", placeholder: "::placeholder", // attribute selectors open: "[open]", // nested selectors hover disabled: ":hover:disabled", hover before: ":hover::before", hover after: ":hover::after" )
All the selectors that can be specified in generators, and the associated suffix for the classes. The key is used in the class name, and the value is used as the selector. e.g. If the first
selector is enabled for a generator, it will create classes such as .first\:bg-black:first-child
.
The selector name can also be a space-separated list for nested selectors. e.g. hover first: ":hover:first-child"
will create .hover\:first\:classname:hover:first-child
classes. We included a few by default, but add your own using $extend as needed. Using a space in the key name is to ensure the configured $separator
is used in the generated class.
specs
type:
map
default:( base: [ aspect-ratio background-color box-shadow box-sizing color font-family font-size font-style font-variant font-weight height letter-spacing line-height margin max-height max-width min-height min-width opacity padding text-align text-decoration text-indent text-overflow text-transform vertical-align white-space width ], hover: [], dark: ( base: [] ), responsive: ( base: [], dark: ( base: [] ) ) )
Rather than include every single possible utility class in the final CSS (which would result in an enormous file), you can add or remove specific classes in the config—which we call "generators". Not using dark mode? Set those generators to an empty list. Want to add utility classes for a specific pseudo selector, or even nested pseudo selectors? Add those to the generators list. We added some sane defaults that will work for most sites, but you will need to add custom generator lists for anything beyond that.
This is different from other systems like Tailwind, which use a just-in-time method where a file watcher scans your HTML files for classes being used and includes them in the outputted CSS. We decided to leave that up to the configuration. It's a bit more work on the developer's side to manually include the class variations they need, but it's more predicable in the end.
See selectors for a list of available selectors. dark
can be set to a nested list of selectors to support dark classes, and responsive
(or individual screens such as sm
and md
) can be set to a nested list to support responsive classes. Dark responsive classes can be enabled with by setting dark
to a nested list within responsive
. If you find yourself with duplicated lists of generators, simply use a Sass variable.
See all the available generators.
specs
type:
map
default: none
This can be used to append custom values onto config maps and lists while retaining the existing values. For example:
$extend: (
screens: (
xxl: 2304px
)
)
Will result in these screens:
(
xs: 640px,
sm: 768px,
md: 1024px,
lg: 1280px,
xl: 1792px,
xxl: 2304px
)
Note that key names nested within $extend
are not prefixed with $
.
These functions are useful for getting style system values inside custom classes.
screen($name)
to get values from $screensspace($index)
to get values from $spacingfont($name)
to get values from $font-stacksfont-weight($name)
to get values from $font-weightsfont-size($name)
to get values from $font-sizesfont-lead($name)
to get values from $font-leadingfont-track($name)
to get values from $font-trackingbox-shadow($name)
to get values from $box-shadowscolor($name-$index)
to get values from $colorscolor($name)
to get values from $brand-colors
For example:
.foobar {
color: ss.color(gray-8);
padding: ss.space(6);
}
Will result in:
.foobar {
color: #e6e9eb;
padding: 2rem;
}
These mixins are useful for defining custom breakpoints using the sizes from $screens. For example:
.foobar {
padding: ss.space(6);
@include ss.media-up-to(md) {
padding: ss.space(8);
}
}
Will result in:
.foobar {
padding: 2rem;
}
@media (min-width: 1024px) {
.foobar {
padding: 3rem;
}
}
media-up-to($screen)
Generates a media query using min-width
(the given screen size or larger):
@include ss.media-up-to(md) {}
Will result in:
@media (min-width: 1024px) {}
media-down-to($screen)
Generates a media query using max-width
(the given screen size or smaller) with 0.5px subtracted from the screen size:
@include ss.media-down-to(md) {}
Will result in:
@media (max-width: 1023.5px) {}
media-only($screen)
Generates a media query using min-width
and max-width
to target a single screen size:
@include ss.media-only(md) {}
Will result in:
@media (min-width: 1024px) and (max-width: 1279.5px) {}
media-between($screen1, $screen2)
Generate a media query using min-width
and max-width
to target screen sizes between the two specified:
@include ss.media-between(sm, lg) {}
Will result in:
@media (min-width: 768px) and (max-width: 1279.5px) {}