A fast, lightweight SCSS-like preprocessor that runs anywhere JavaScript runs.
Built for real-time compilation in browsers, editors, and build tools.
- Table of Contents
- Benchmarks
- Why rics?
- Packages
- Installation
- Quick Start
- Features
- API
- Configuration
- Built-in Functions
- Native CSS Pass-through
- License
Auto-generated. Run
pnpm benchin the benchmarks folder to regenerate.
| Preprocessor | ops/sec | Comparison |
|---|---|---|
| rics | 897 | fastest |
| stylus | 334 | 2.7x slower |
| sass | 279 | 3.2x slower |
| less | 221 | 4.1x slower |
| Package | Size | Dependencies | Comparison |
|---|---|---|---|
| rics | 12.7 KB | 0 | smallest |
| less | 49.0 KB | 0 | 4x larger |
| stylus | 82.8 KB | 5 | 7x larger |
| sass | 686.0 KB | 1 | 54x larger |
Drop-in compatible — All valid CSS is valid rics. Use your existing stylesheets and progressively adopt preprocessor features.
rics vs Stylis — Stylis handles nesting and autoprefixing (~3KB), but that's it. rics adds full SCSS features — variables, mixins, functions, loops, conditionals, color manipulation — while staying small (~10KB minzipped) and fast.
rics vs Sass/Less/Stylus — These need Node.js or WASM. rics runs anywhere JavaScript runs, compiles in milliseconds, and has zero dependencies.
| Feature | rics | Stylis | Sass | Less |
|---|---|---|---|---|
| Variables | Yes | No | Yes | Yes |
| Nesting | Yes | Yes | Yes | Yes |
| Mixins | Yes | No | Yes | Yes |
| Functions | Yes | No | Yes | Yes |
| Loops | Yes | No | Yes | Yes |
| Color functions | Yes | No | Yes | Yes |
| Browser-native | Yes | Yes | No | No |
| Zero dependencies | Yes | Yes | No | No |
| Package | Description | Size |
|---|---|---|
| rics | Core compiler | ~40KB |
| rics-cli | Command-line interface | ~2KB |
| vite-plugin-rics | Vite plugin | ~1KB |
| webpack-loader-rics | Webpack loader | ~0.5KB |
| codemirror-lang-rics | CodeMirror 6 support | ~4KB |
| prettier-plugin-rics | Prettier formatter | ~1KB |
| eslint-plugin-rics | ESLint plugin | ~2KB |
| rics-vscode | VS Code extension | - |
# Core compiler
npm install rics
# CLI (global)
npm install -g rics-cli
# Build tools
npm install vite-plugin-rics # Vite
npm install webpack-loader-rics # Webpack
# Editor support
npm install codemirror-lang-rics # CodeMirror 6
# Code quality
npm install prettier-plugin-rics # Prettier
npm install eslint-plugin-rics # ESLintimport { compile } from "rics";
const css = compile(`
$primary: #f43f5e;
$radius: 8px;
.button {
background: $primary;
border-radius: $radius;
padding: 12px 24px;
&:hover {
background: darken($primary, 10%);
}
}
`);# Compile to stdout
rics styles.rics
# Compile to file
rics styles.rics -o styles.css
# Watch mode
rics styles.rics -o styles.css --watch
# Minify output
rics styles.rics -o styles.css --minify// vite.config.ts
import { defineConfig } from "vite";
import { ricsPlugin } from "vite-plugin-rics";
export default defineConfig({
plugins: [ricsPlugin()],
});// main.ts
import "./styles.rics"; // Auto-compiled and injected// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.rics$/,
use: ["style-loader", "css-loader", "webpack-loader-rics"],
},
],
},
};import { EditorView, basicSetup } from "codemirror";
import { ricsLanguage, ricsLinter } from "codemirror-lang-rics";
const editor = new EditorView({
extensions: [basicSetup, ricsLanguage(), ricsLinter({ delay: 300 })],
parent: document.getElementById("editor"),
});// .prettierrc
{
"plugins": ["prettier-plugin-rics"]
}// eslint.config.js
import ricsPlugin from "eslint-plugin-rics";
export default [
{
files: ["**/*.rics"],
...ricsPlugin.configs.recommended,
plugins: { rics: ricsPlugin },
},
];$base: 4px;
$primary: #f43f5e;
.element {
padding: $base * 4; // 16px
margin: $base * 2 $base * 3; // 8px 12px
width: 100% - 20px;
color: $primary;
}.card {
padding: 16px;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&--featured {
border: 2px solid gold;
}
&__title {
font-size: 24px;
}
.icon {
margin-right: 8px;
&.left {
margin-left: 0;
}
}
}@mixin button($bg, $color: white, $radius: 4px) {
background: $bg;
color: $color;
border-radius: $radius;
border: none;
cursor: pointer;
&:hover {
background: darken($bg, 10%);
}
}
.btn-primary {
@include button(#f43f5e);
}
.btn-secondary {
@include button(#6366f1, white, 8px);
}@function rem($px) {
@return #{$px / 16}rem;
}
@function spacing($n) {
@return 4px * $n;
}
.text {
font-size: #{rem(18)}; // 1.125rem
padding: spacing(4); // 16px
margin-bottom: spacing(6); // 24px
}// Generate utility classes
@for $i from 1 through 8 {
.gap-#{$i} {
gap: #{$i * 4}px;
}
.p-#{$i} {
padding: #{$i * 4}px;
}
.m-#{$i} {
margin: #{$i * 4}px;
}
}
// Iterate maps
$colors: (
primary: #f43f5e,
success: #22c55e,
warning: #f59e0b,
);
@each $name, $color in $colors {
.text-#{$name} {
color: $color;
}
.bg-#{$name} {
background: $color;
&:hover {
background: darken($color, 10%);
}
}
}@mixin theme($mode) {
@if $mode == dark {
background: #0a0a0a;
color: #fafafa;
} @else if $mode == light {
background: #ffffff;
color: #0a0a0a;
} @else {
background: inherit;
}
}
.panel {
@include theme(dark);
}$brand: #f43f5e;
.palette {
--light: #{lighten($brand, 20%)};
--dark: #{darken($brand, 20%)};
--muted: #{desaturate($brand, 30%)};
--complement: #{complement($brand)};
--mix: #{mix($brand, #3b82f6, 50%)};
--transparent: #{rgba($brand, 0.5)};
}Available: lighten, darken, saturate, desaturate, adjust-hue, mix, complement, invert, grayscale, rgba, rgb, hsl, hsla, red, green, blue, alpha, hue, saturation, lightness
$prop: margin;
$side: top;
$i: 5;
.item {
#{$prop}-#{$side}: 10px;
}
.col-#{$i} {
width: percentage($i / 12);
}
[data-count="#{$i * 10}"] {
content: "#{$i} items";
}Returns compiled CSS string.
const css = compile(scss);Returns full compilation result with errors, warnings, and stats:
const result = compileWithDetails(scss);
console.log(result.css);
console.log(result.errors); // CompileError[]
console.log(result.warnings); // CompileWarning[]
console.log(result.stats); // { duration, rules, iterations, inputSize, outputSize }Non-blocking compilation for large inputs:
const result = await compileAsync(largeStylesheet);compile(scss, {
timeout: 5000, // Max compilation time (ms)
maxIterations: 10000, // Max loop iterations
maxNestingDepth: 64, // Max selector nesting
minify: false, // Minify output
strictMode: false, // Throw on first error
});round, ceil, floor, abs, min, max, percentage, random
str-length, str-slice, str-index, str-insert, to-upper-case, to-lower-case, quote, unquote
length, nth, join, append, index
map-get, map-keys, map-values, map-has-key
type-of, unit, unitless, if, inspect
Modern CSS features work unchanged:
- Custom properties:
var(--color),env(safe-area-inset-top) - Functions:
calc(),min(),max(),clamp() - At-rules:
@media,@container,@supports,@layer,@scope - Selectors:
:has(),:is(),:where(),@starting-style
MIT
Built by Better Lyrics