
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@gesslar/sassy
Advanced tools
Full Documentation at https://sassy.gesslar.io.
Transform VS Code theme development from tedious to delightful.
Stop wrestling with 800+ disconnected hex codes. Create beautiful, maintainable themes with semantic variables, colour functions, and design systems that actually make sense.
VS Code theme development is a nightmare:
Write themes like a human, compile for VS Code:
Before (traditional):
{
"editor.background": "#1e1e1e",
"editor.foreground": "#e6e6e6",
"statusBar.background": "#002e63",
"panel.background": "#1a1a1a"
}
After (Sassy):
palette:
blue: "#4b8ebd"
white: "#e6e6e6"
dark: "#1a1a1a"
vars:
accent: $$blue
std:
fg: $$white
bg: $$dark
bg.panel: lighten($(std.bg), 15)
bg.accent: darken($(accent), 15)
theme:
colors:
"editor.background": $(std.bg.panel)
"editor.foreground": $(std.fg)
"statusBar.background": $(std.bg.accent)
"panel.background": $(std.bg)
Now when you want to adjust contrast, change one variable and watch it cascade through your entire theme.
No installation needed - use with npx:
# Create your first theme
npx @gesslar/sassy build my-theme.yaml
# Watch mode for development
npx @gesslar/sassy build my-theme.yaml --watch
# Custom output location
npx @gesslar/sassy build -o ./themes my-theme.yaml
# Basic compilation
npx @gesslar/sassy build <theme-file>
# Multiple files at once
npx @gesslar/sassy build theme1.yaml theme2.yaml theme3.yaml
# Watch for changes (rebuilds automatically)
npx @gesslar/sassy build --watch my-theme.yaml
# Custom output directory
npx @gesslar/sassy build --output-dir ./my-themes my-theme.yaml
# See the compiled JSON without writing files
npx @gesslar/sassy build --dry-run my-theme.yaml
# Silent mode (only show errors)
npx @gesslar/sassy build --silent my-theme.yaml
# Debug mode (detailed error traces)
npx @gesslar/sassy build --nerd my-theme.yaml
# Lint themes for potential issues
npx @gesslar/sassy lint my-theme.yaml
# Fail on warnings too (useful in CI)
npx @gesslar/sassy lint --strict my-theme.yaml
| Option | Description |
|---|---|
-w, --watch | Watch files and rebuild on changes |
-o, --output-dir <dir> | Specify output directory |
-n, --dry-run | Print JSON to stdout instead of writing files |
-s, --silent | Only show errors (useful for scripts) |
--nerd | Verbose error mode with stack traces |
See what a colour variable resolves to:
npx @gesslar/sassy resolve --color editor.background my-theme.yaml
Debug tokenColors syntax highlighting:
npx @gesslar/sassy resolve --tokenColor keyword.control my-theme.yaml
Debug semantic token colours:
npx @gesslar/sassy resolve --semanticTokenColor variable.readonly my-theme.yaml
This shows you the complete resolution chain for any theme property, displaying each step of variable substitution and function evaluation with colour-coded output.
| Option | Description |
|---|---|
-c, --color <key> | Resolve a specific color property to its final value |
-t, --tokenColor <scope> | Resolve tokenColors for a specific scope |
-s, --semanticTokenColor <token> | Resolve semantic token colors for a specific token type |
--nerd | Show detailed error traces if resolution fails |
Validate your theme for common issues:
npx @gesslar/sassy lint my-theme.yaml
The lint command performs comprehensive validation of your theme files to catch common issues that could cause unexpected behaviour or poor maintainability.
The linter performs four types of validation:
Detects when the same syntax scope appears in multiple tokenColors rules:
# ❌ This will trigger a warning
theme:
tokenColors:
- name: "Keywords"
scope: "keyword.control, keyword.operator"
settings: { foreground: "$(accent)" }
- name: "Control Keywords"
scope: "keyword.control" # Duplicate!
settings: { foreground: "$(primary)" }
Why this matters: The second rule will never be applied since the first rule
already matches keyword.control tokens.
Catches references to variables that don't exist:
# ❌ This will trigger an error
theme:
tokenColors:
- name: "Comments"
scope: "comment"
settings: { foreground: "$(nonexistent.variable)" } # Error!
Identifies variables defined but never used in tokenColors:
# ⚠️ This will trigger a warning if never used
vars:
scope:
unused_color: "#ff0000" # Warning if not referenced anywhere
Note: Only checks variables under scope.* since other variables might be
used in the colors section.
Detects when broad scopes mask more specific ones due to rule ordering:
# ❌ This will trigger a warning
theme:
tokenColors:
- name: "All Keywords"
scope: "keyword" # Broad scope
settings: { foreground: "$(primary)" }
- name: "Control Keywords"
scope: "keyword.control" # More specific, but will never match!
settings: { foreground: "$(accent)" }
Why this matters: The second rule will never be applied because the first
rule already matches all keyword.* tokens. Reorder rules from most specific
to least specific.
| Option | Description |
|---|---|
--strict | Treat warnings as errors — exits 1 if any warnings are found |
--nerd | Show detailed error traces if linting fails |
# my-awesome-theme.yaml
config:
name: "My Awesome Theme"
type: dark
palette:
# Raw colour values — self-contained, evaluated first
blue: "#4b8ebd"
green: "#4ab792"
red: "#b74a4a"
white: "#e6e6e6"
dark: "#1a1a1a"
vars:
# Semantic relationships referencing palette via $$
primary: $$blue
success: $$green
error: $$red
std:
fg: $$white
bg: $$dark
accent: $(primary)
bg.accent: darken($(accent), 15)
theme:
colors:
# Editor
"editor.foreground": $(std.fg)
"editor.background": $(std.bg)
"editor.selectionBackground": $(std.bg.accent)
# UI
"statusBar.background": $(std.bg.accent)
"activityBar.background": $(std.bg)
"sideBar.background": $(std.bg)
Sassy is built on Culori, a comprehensive colour manipulation library. This means if Culori supports it, Sassy supports it automatically - no configuration needed.
While Sassy provides common functions like lighten(), darken(), and
mix(), you have access to the entire spectrum of colour formats:
palette:
# Use any colour space Culori understands
lab_colour: lab(50 20 -30) # LAB colour space
hwb_colour: hwb(180 30% 20%) # HWB (Hue-Whiteness-Blackness)
lch_colour: lch(70 40 180) # LCH colour space
p3_colour: color(display-p3 0.4 0.8 0.2) # Display P3 gamut
rec2020: color(rec2020 0.42 0.85 0.31) # Rec. 2020 colour space
primary: oklch(0.6, 20, 220)
vars:
# Mix and match freely
secondary: mix($$primary, lab(80 -20 40), 30)
accent: lighten(hwb(240 20% 10%), 15)
The rule is simple: Write any colour expression that Culori can parse, and Sassy will handle it. No need to memorize function lists or check compatibility - if it's a valid colour, it works.
Learn More: Explore the full range of supported colour formats and functions in the Culori documentation.
Make colours that work together:
| Function | Example | Result |
|---|---|---|
lighten(colour, %=0-100) | lighten($(bg), 25) | 25% lighter background |
darken(colour, %=0-100) | darken($(accent), 30) | 30% darker accent |
alpha(colour, alpha=0-1) | alpha($(brand), 0.5) | Set exact transparency |
fade(colour, alpha=0-1) | fade($(accent), 0.5) | Reduce opacity by 50% |
solidify(colour, alpha=0-1) | solidify($(bg.accent), 0.3) | Increase opacity by 30% |
mix(colour1, colour2, %=0-100) | mix($(fg), $(accent), 20) | Blend 20% accent |
mix(colour1, colour2) | mix($(fg), $(accent)) | Blend 50% accent |
invert(colour) | invert($(fg)) | Perfect opposite |
hsv(h=0-255, s=0-255, v=0-255) | hsv(50, 200, 180) | HSV colour (hue 50, saturation 200, value 180) |
hsva(h=0-255, s=0-255, v=0-255, a=0-1) | hsva(50, 200, 180, 0.5) | HSV with 50% opacity |
hsl(h=0-360, s=0-100, l=0-100) | hsl(200, 50, 40) | HSL colour (200° hue, 50% saturation, 40% lightness) |
hsla(h=0-360, s=0-100, l=0-100, a=0-1) | hsla(200, 50, 40, 0.5) | HSL with 50% opacity |
rgb(r=0-255, g=0-255, b=0-255) | rgb(139, 152, 255) | RGB colour (139 red, 152 green, 255 blue) |
rgba(r=0-255, g=0-255, b=0-255, a=0-1) | rgba(139, 152, 255, 0.5) | RGB with 50% opacity |
oklch(l=0-1, c=0-100, h=0-360) | oklch(0.7, 25, 180) | OKLCH colour (70% lightness, 25 chroma, 180° hue) |
oklcha(l=0-1, c=0-100, h=0-360, a=0-1) | oklcha(0.5, 30, 45, 0.8) | OKLCH with 80% opacity |
css(name) | css(tomato) | CSS named colour (tomato, skyblue, etc.) |
Note: In all of these functions,
colourcan be a raw hex (#ff66cc), a variable ($(accent)), a CSS named colour (css(tomato)), or another colour function (rgba(255, 100, 200, 0.5),darken($(bg), 20),oklcha(0.7, 25, 180, 0.8)).
Use CSS colour names with the css() function:
palette:
# CSS named colours
danger: css(crimson)
ocean: css(deepskyblue)
nature: css(forestgreen)
vars:
# Mix palette colours with functions
muted_red: fade($$danger, 0.6)
light_blue: lighten($$ocean, 40)
Reference: See the complete list of CSS named colours at MDN Web Docs or Wikipedia.
Use any of these syntaxes (they're identical):
vars:
accent: "#4b8ebd"
# All equivalent:
variant1: $(accent) # Recommended
variant2: $accent # Short form
variant3: ${accent} # Braced form
The $$ prefix is shorthand for referencing palette.* values:
palette:
cyan: "#56b6c2"
vars:
# All equivalent — resolve to palette.cyan:
a: $$cyan
b: $($cyan)
c: ${$cyan}
This expansion happens before any variable resolution, so downstream tools
(resolve, lint) see the canonical $palette.cyan form.
# Create a new theme file
touch ocean-theme.yaml
# Start watching for changes
npx @gesslar/sassy build --watch ocean-theme.yaml
After compilation, you'll get a .color-theme.json file:
~/.vscode/extensions/my-themes/themes/yo code to create a theme extensionCtrl+K Ctrl+T in VS Code to switch themesWith watch mode, every save triggers recompilation. VS Code will automatically reload your theme changes.
Sassy generates standard VS Code theme files:
my-theme.yaml → my-theme.color-theme.json
The output file name is based on your input file, with .color-theme.json
extension.
Break your themes into reusable components using the import system:
# palette.yaml
palette:
blue: "#4b8ebd"
green: "#4ab792"
red: "#b74a4a"
orange: "#b36b47"
---
# my-theme.yaml
config:
name: "My Theme"
type: dark
import:
- "./palette.yaml"
vars:
# Use imported palette via $$ alias
accent: $$blue
# Build your design system
std:
fg: "#e6e6e6"
bg: "#1a1a1a"
accent: $(accent)
bg.accent: darken($(accent), 15)
theme:
colors:
"editor.foreground": $(std.fg)
"editor.background": $(std.bg)
"statusBar.background": $(std.bg.accent)
Sassy supports importing different types of theme components:
config:
import:
- "./shared/colours.yaml" # Variables and base config
- "./shared/ui-colours.yaml" # VS Code color definitions
- "./shared/syntax.yaml" # Syntax highlighting rules
- "./shared/semantic.yaml" # Semantic token colours
Import Format:
Imports are a simple array of file paths. Each file gets merged into your theme:
["./file1.yaml", "./file2.yaml", "./file3.yaml"].yaml format are supportedMerge Order:
The merge behaviour depends on the type of theme content:
Objects (composable): palette, colors, semanticTokenColors, vars, config
Later sources override earlier ones using deep object merging.
Arrays (append-only): tokenColors
tokenColors (in import order)tokenColors (appended last)Why different? VS Code reads tokenColors from top to bottom and stops at the first matching rule. This means:
Examples:
If an import defines keyword.control and your main file also defines keyword.control, VS Code will use the imported version because it appears first in the final array.
If your import has a broad rule like storage and your main file has a specific rule like storage.type, the broad storage rule will match first and your specific storage.type rule will never be used.
Tip: If you're unsure about rule precedence or conflicts, run
npx @gesslar/sassy lint your-theme.yamlto see exactly what's happening with yourtokenColors.
Perfect for theme development - see changes instantly:
npx @gesslar/sassy build my-theme.yaml --watch
Now edit your YAML file and watch VS Code update automatically!
Sassy's core is fully usable without the CLI. The builder pattern on Theme
and standalone engine classes (Lint, Resolve, Proof) make it embeddable
in editors, extensions, and build tools.
import {DirectoryObject} from '@gesslar/toolkit'
import {Theme, Lint, Resolve} from '@gesslar/sassy'
const cwd = DirectoryObject.fromCwd()
const file = cwd.getFile('my-theme.yaml')
// Build
const theme = new Theme()
.setCwd(cwd)
.setThemeFile(file)
.setOptions({outputDir: './dist'})
await theme.load()
await theme.build()
const output = theme.getOutput() // compiled theme object
// Lint
const issues = await new Lint().run(theme) // structured issue data
// Resolve
const data = await new Resolve().color(theme, 'editor.background') // resolution trail
Cache is optional — load() falls back to direct file reads without one.
Engine methods automatically load() and build() the theme if needed, so
you can pass a freshly constructed Theme directly to any engine.
See the full API reference for
details on all engine classes and return shapes.
# ✅ Raw colours go in palette
palette:
red: "#b74a4a"
green: "#4ab792"
dark: "#1a1a1a"
# ✅ Semantic meaning goes in vars
vars:
status:
error: $$red
success: $$green
ui:
background: $$dark
surface: lighten($(ui.background), 15)
palette:
base: "#4b8ebd"
gray: "#808080"
# OKLCH for perceptually uniform colours
primary: oklch(0.6, 20, 220)
accent: oklch(0.7, 25, 45)
vars:
# Colours that harmonize automatically
harmonies:
lighter: lighten($$base, 20)
darker: darken($$base, 20)
complement: mix($$base, invert($$base), 50)
muted: mix($$base, $$gray, 30)
Always test your themes with actual code files to see how syntax highlighting looks with your colour choices.
Check out the /examples folder for complete theme files showing
different approaches and techniques.
Theme not appearing in VS Code:
.color-theme.jsonCtrl+Shift+P → "Developer: Reload Window")Compilation errors:
# See detailed error information
npx @gesslar/sassy build --nerd my-theme.yaml
# Check what a specific variable resolves to
npx @gesslar/sassy resolve --color problematic.variable my-theme.yaml
Variables not resolving:
Watch mode not updating:
.yaml file (not the compiled .color-theme.json)The /examples containes complete, compilable theme files in both formats.
I iterate on my own cadence and I do not keep a diary. But, each PR that I submit I run through Graphite which does an amazing job of itemising all of the changes. So, if you wanna know, you now know where you can know the new know.
sassy is released into the public domain under the Unlicense.
This package includes or depends on third-party components under their own licenses:
| Dependency | License |
|---|---|
| @gesslar/colours | Unlicense |
| @gesslar/toolkit | Unlicense |
| chokidar | MIT |
| color-support | ISC |
| commander | MIT |
| culori | MIT |
| globby | MIT |
| yaml | ISC |
| yaml-eslint-parser | MIT |
Make gorgeous themes that speak as boldly as you do.
FAQs
Make gorgeous themes that speak as boldly as you do.
The npm package @gesslar/sassy receives a total of 30 weekly downloads. As such, @gesslar/sassy popularity was classified as not popular.
We found that @gesslar/sassy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.