Skip to content

TheEdgesofBen/combocss

Repository files navigation

Logo

CSS toolkit for combining CSS properties


ComboCSS scans your source files for class names and generates CSS from a compact class syntax. Utility classes can be grouped into reusable custom classes called combos.

ComboCSS is currently recommended with Vite/PostCSS setups.

Installation

npm install -D combocss postcss
npx combo init

Add ComboCSS to your PostCSS config:

import { plugin } from "combocss";

export default {
    plugins: [plugin()],
};

Basic syntax

A generated class has at least two parts:

camelCaseProperty-value

Example:

<div class="backgroundColor-blue display-inlineFlex"></div>

Generates:

.backgroundColor-blue {
    background-color: blue;
}

.display-inlineFlex {
    display: inline-flex;
}

Values with spaces use dashes:

<div class="border-1px-solid-black"></div>

Generates:

.border-1px-solid-black {
    border: 1px solid black;
}

CSS variables are supported as values:

<div class="backgroundColor-var(--color-primary)"></div>
<div class="border-1px-solid-var(--cyan)"></div>

Generates:

.backgroundColor-var\(--color-primary\) {
    background-color: var(--color-primary);
}

.border-1px-solid-var\(--cyan\) {
    border: 1px solid var(--cyan);
}

Function values

CSS functions can be used directly. Replace spaces inside function arguments with underscores:

<div class="width-calc(100%_-_16px)"></div>

Generates:

.width-calc\(100\%_-_16px\) {
    width: calc(100% - 16px);
}

CamelCase function names are converted to CSS function names:

<div class="maskImage-linearGradient(to_bottom,_black,_transparent_82%)"></div>

Generates:

.maskImage-linearGradient\(to_bottom\,_black\,_transparent_82\%\) {
    mask-image: linear-gradient(to bottom, black, transparent 82%);
}

Raw values for edge cases

Use braces when a value should be passed through as raw CSS:

<div class="fontFamily-{Inter, system-ui, sans-serif}"></div>

Generates:

.fontFamily-\{Inter\,_system-ui\,_sans-serif\} {
    font-family: Inter, system-ui, sans-serif;
}

Raw values are useful for complex values such as font stacks, slash-separated values, multiple backgrounds, and grid template strings:

font-{italic 700 1rem/1.5 Inter, system-ui}
background-{linear-gradient(...), radial-gradient(...)}
gridTemplateAreas-{"header header" "sidebar main"}

Combos

Combos group classes into a named class. Add custom CSS files in combo.config.json under custom.

.button-base {
    @combo minWidth-200px borderRadius-8px;
}

.button-primary {
    @combo button-base backgroundColor-#ff1342;
}
<button class="button-primary"></button>

Combos may reference other combos. Circular combo chains are reported as diagnostics.

Custom selectors that should generate ComboCSS output must include @combo or @shortcut. Plain selectors without either directive are ignored by ComboCSS and should live in regular CSS files.

Shortcuts

Shortcuts map a short prefix to a CSS property:

.ml {
    @shortcut marginLeft;
}
<div class="ml-32px"></div>

Generates margin-left: 32px.

Responsive prefixes

Configure breakpoints in combo.config.json:

{
    "breakpoints": {
        "tablet": "700px",
        "desktop": "1024px",
        "wide": "1440px"
    }
}

Use them as prefixes:

<div class="marginLeft-8px tablet:marginLeft-12px desktop:marginLeft-14px wide:marginLeft-16px"></div>

ComboCSS generates base classes first, then responsive classes from the smallest breakpoint to the largest breakpoint:

.marginLeft-8px {
    margin-left: 8px;
}

@media (min-width: 700px) {
    .tablet\:marginLeft-12px {
        margin-left: 12px;
    }
}

@media (min-width: 1024px) {
    .desktop\:marginLeft-14px {
        margin-left: 14px;
    }
}

@media (min-width: 1440px) {
    .wide\:marginLeft-16px {
        margin-left: 16px;
    }
}

Breakpoint sorting supports px, rem, and em. rem and em are sorted as value * 16. Unknown units fall back to config order.

Important and negative values

<div class="!height-24px -marginTop-8px"></div>

Generates important and negative declarations:

.!height-24px {
    height: 24px !important;
}

.-marginTop-8px {
    margin-top: -8px;
}

Pseudo selectors

Utility pseudo selectors can be appended after the main class:

<div class="marginLeft-8px:hover"></div>
<div class="backgroundColor-red:has(.item:hover)"></div>

If no matching custom selector exists, ComboCSS generates escaped utility pseudo classes:

.marginLeft-8px\:hover:hover {
    margin-left: 8px;
}

Custom selectors win over utility pseudo generation. If combo.custom.css defines a selector like this:

.app-shell {
    @combo position-relative;
}

.app-shell:before {
    @combo content-'' position-absolute;
}

using only the base class:

<div class="app-shell"></div>

generates both selectors as real CSS selectors:

.app-shell {
    position: relative;
}

.app-shell:before {
    content: '';
    position: absolute;
}

ComboCSS does not generate the broken escaped selector .app-shell\:before:before. Complex selectors are parsed with parentheses awareness, but dynamic/generated class strings may still need to be added to the classes safelist.

JIT mode

JIT mode is the recommended way to use ComboCSS in Vite/PostCSS projects. It scans the files configured in combo.config.json, generates only the classes it finds, writes them to the configured output CSS file, and watches for changes during development.

Add ComboCSS to your PostCSS config:

import { plugin } from "combocss";

export default {
    plugins: [plugin()],
};

Import the generated output CSS file in your app entry:

import "./src/index.css";

Example combo.config.json JIT setup:

{
    "input": ["index.html", "src/**/*.{vue,js,ts,jsx,tsx}"],
    "output": "src/index.css",
    "custom": ["src/combo.custom.css"],
    "classes": [],
    "cache": ".combocss",
    "plugins": []
}

During development, ComboCSS watches:

  • files matched by input
  • files matched by custom
  • combo.config.json

The generated cache is stored in .combocss/store by default. Add .combocss to .gitignore.

Post-processing plugins are opt-in. By default, plugins is [] so JIT mode does not crash because of external PostCSS plugin parsing/configuration. Use plugins: ["autoprefixer"] if you want generated CSS to be autoprefixed. Add "stylelint" only when your project has a Stylelint config.

If post-processing fails, ComboCSS writes the generated CSS without post-processing and logs a warning instead of crashing the watcher.

For classes built dynamically at runtime, add the final class names to the classes safelist:

{
    "classes": ["display-flex", "backgroundColor-red:hover", "tablet:height-32px"]
}

Dynamic template literal classes are ignored by extractors because their final values are not statically knowable:

<template>
    <div :class="[`solar-shell-${body.orbit}`]"></div>
</template>

Safelist the possible final classes instead.

You can also run the watcher directly from the CLI:

npx combo watch

Standalone mode

import { combocss } from "combocss";

const css = await combocss(["marginLeft-8px"]);
console.log(css);

Custom combos can be passed as raw CSS:

const css = await combocss(["button-primary"], {
    custom: `
        .button-primary {
            @combo display-flex backgroundColor-red;
        }
    `,
});

Standalone mode returns an empty string when there are no classes.

Optional diagnostics can be collected:

const diagnostics = [];
const css = await combocss(["button-primary"], {
    custom: `.button-primary { @combo button-primary; }`,
    diagnostics,
});

CLI

npx combo init
npx combo init --force
npx combo process
npx combo process --config combo.config.json
npx combo watch
npx combo watch --config combo.config.json
npx combo validate
npx combo clean

Config

Default combo.config.json:

{
    "input": ["index.html", "src/**/*.{vue,js,ts,jsx,tsx}"],
    "output": "src/index.css",
    "custom": ["custom.css"],
    "classes": [],
    "cache": ".combocss",
    "strict": false,
    "plugins": [],
    "ignore": {
        "prefix": [],
        "suffix": [],
        "class": []
    },
    "breakpoints": {
        "tablet": "600px",
        "tabletAndPC": "1024px",
        "pc": "1440px",
        "ultrawide": "1921px"
    }
}

Config fields

  • input: source globs ComboCSS scans.
  • output: generated CSS file.
  • custom: custom combo/shortcut CSS globs.
  • classes: safelisted classes to always generate.
  • cache: project-local cache directory. Defaults to .combocss; the internal store is .combocss/store.
  • strict: emits warnings for invalid classes that cannot create declarations.
  • plugins: optional post-processing plugins. Defaults to [] so JIT mode never fails because of external PostCSS plugin parsing/configuration. Use ["autoprefixer"] if you want generated CSS to be autoprefixed. Add "stylelint" only if your project has a Stylelint config.
  • ignore.prefix: class prefixes to ignore.
  • ignore.suffix: class suffixes to ignore.
  • ignore.class: exact class names to ignore.
  • breakpoints: responsive prefix map. Generated output is sorted mobile-first from smallest to largest breakpoint.

Extraction limitations

ComboCSS extracts literal classes from HTML, Vue, JS, TS, JSX, and TSX. It supports:

  • static class="..."
  • simple Vue :class / v-bind:class object and array string literals
  • JSX className="..."
  • plain JS/TS string literals that look like ComboCSS classes

Dynamic template literal classes containing ${...} are ignored. For highly dynamic class generation, add classes to classes in combo.config.json.

Browser and shared core

The browser entry is intentionally thin and imports the same standalone implementation as Node:

import { combocss } from "combocss/browser";

The core parser/generator exports are available for advanced integrations:

import { createCSSClassesData, getClassParts } from "combocss/core";

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors