Skip to content

Introduction

ECSS (Extended CSS) is a superset of CSS that compiles at build time. It lets you describe a component's states and variants directly in the stylesheet, without duplicating logic in JavaScript, while giving you full typing in TypeScript and a minimal runtime.

What is ECSS?

Any valid CSS is valid ECSS. On top of the familiar syntax, ECSS adds a handful of directives that turn an .ecss file into a typed component:

  • @block — a named style component;
  • @param — a parameter that controls which rules apply;
  • @if / @elseif / @else — conditional styles based on parameters;
  • @enum, @const, @element, @import, @external — enums, constants, nested elements, and working with multiple files.

The compiler turns an .ecss file into plain static CSS plus a typed module that you import into your application code.

What problem does ECSS solve

Usually a component's state logic is spread across two places: the classes are declared in CSS, while the decision of "which class to apply" lives in JavaScript. Every new variant requires changes in both places, and there is nothing to guarantee the link between a prop value and a class.

ECSS moves this logic into the styles. The component knows nothing about any classes — it only passes parameters.

What it looks like

ecss
/* Button.ecss */
@enum Variant {
  values: "primary", "danger", "ghost";
}

@block Button {
  @param --variant Variant;
  @param --disabled? boolean;

  display: inline-flex;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;

  @if (--variant == "primary") {
    background: #646cff;
    color: #fff;
  }

  @if (--disabled) {
    opacity: 0.4;
    cursor: not-allowed;
  }
}

Import the generated component and pass parameters — building the className and applying the right styles happens automatically:

tsx
import { EButton } from './Button.ecss';

<EButton as="button" params={{ variant: 'primary' }}>
  Button
</EButton>;

TypeScript knows the allowed values of variant from the @enum declaration — you can't pass a nonexistent variant.

Key features

  • A superset of CSS. Familiar syntax; you add only what you need.
  • States without JS. Parameters (@param) and conditional styles (@if) are described in the .ecss file, not in the component.
  • Full typing. Precise types are generated for every *.ecss import — autocompletion and type checking work out of the box.
  • Any framework. Adapters generate components for React, Vue, Svelte, and SolidJS; there is also an adapter for plain DOM.
  • Build time, not runtime. Static CSS reaches the browser — there is no runtime overhead, and it works with SSR without additional setup.
  • Minimal runtime. Only a tiny helper for assembling classes and CSS variables is added to the bundle.
  • Editor support. The VS Code extension provides syntax highlighting, error diagnostics, and hover hints.

How it works

  1. You write .ecss files with blocks, parameters, and conditions.
  2. The compiler (@ecss/compiler) turns them into static CSS and a typed JS module, and the adapter turns them into components for your framework.
  3. The bundler plugin (@ecss/vite-plugin) wires it all into your project, and the configuration (@ecss/config) is shared and not tied to a specific bundler.

Next steps