CSS Blocks is an ergonomic, component-oriented CSS authoring system that compiles to high-performance stylesheets.
By combining an opinionated authoring system, build-time analysis and rewriting of templates, and a new type of CSS optimizer, css-blocks breathes new power and ease of use into the technologies and best practices that stylesheet developers already know and love.
Interested in contributing, or just seeing CSS Blocks in action? Head over to CONTRIBUTING.md to learn how to spin up the project!
Table of Contents
With css-blocks added to your project, you receive:
But, most importantly, CSS Blocks is ⚡️Statically Analyzable.
Static analysis means css-blocks can look at your project and know with certainty that any given CSS declaration will, will not, or might under certain conditions, be used on any given element in your templates.
Most stylesheet architectures have to walk a fine line between performance and maintainability. Tilt too far in either direction and either your users or the developers will end up paying the cost. With CSS Blocks, you can focus on making sure your stylesheets are easy to maintain as your application changes, and with the new CSS optimizer, OptiCSS, the small size of your app's production stylesheets after compression will amaze you.
Gone are the days where you spend several minutes debugging your app only to discover a subtle typo that caused a selector to not match – CSS Blocks will give you a build error and suggest possible fixes. With IDE integration, projects using CSS Blocks will be able to quickly navigate to selector definitions that match your current template element and find which template elements match your current selector, autocomplete class names. With CSS Blocks new resolution system, cascade conflicts will be caught for you before you even know they exist and you will never have to fight a specificity war ever again.

CSS Blocks is inspired by CSS Modules, BEM and Atomic CSS
For a full deep-dive of the project architecture, I heavily recommend you review the CSS Blocks Architecture README!
CSS Blocks requires deep integration with your build system and templating language. To learn how to install css-blocks for in your application, please consult the specific docs for your templating system and build system.
CSS Blocks is available for use in the following templating languages:
And has integrations with the following build systems:
Don't see your preferred platform yet?
Learn how to make your own Template Integration or Build System Integration and contribute it back!
CSS Blocks is under active development and there are a number of features that have not yet been implemented! You can get a snapshot of the feature-set state here.
✅ = Implemented | ❌ = Not Implemented | 💀 = Deprecated | 🖌 = In Proposal |
| Status | Feature | Description |
|---|---|---|
| Selectors | ||
| ✅ | :scope |
Scope selector for component root. |
| ✅ | .class |
Class selectors for component sub-elements. |
| ✅ | .class[name] |
State that is applied to scope and class selectors on state existence. |
| ✅ | .class[name="value"] |
Mutually exclusive sub-states for scope and class selectors to be applied when a sub-state value matches. |
| ❌ | [name=value] |
Bare state (not associated with an Originating Element) and optional substate selectors for targeting all elements in the Block that possess the state and/or sub-state. |
| 🖌 | .class[name=value default] |
Default state value to be applied when there is no other match. |
| At Rules | ||
| ✅ | @block local-name from "./file/path.css" |
Reference another Block using a local name. |
| ✅ | @block-debug block-name to channel |
Debug call that will print a block interface to a "channel": comment, stderr, or stdout. |
| ✅ | @block-global block.path |
Declare a Block class or state as public. It may be used as a context selector in other Blocks. |
| Properties | ||
| ✅ | block-name: "custom-name"; |
Provide custom Block names in :scope for a nicer debugging experience. |
| ✅ | implements: block-name; |
A Block can declare that it implements one or more other Block's interfaces in its :scope selector and the compiler will ensure that all of those states and classes are styled locally. |
| ✅ | extends: block-name; |
A Block may specify it extends another Block in its :scope selector to inherit and extend all the class and state implementations therein. |
| ✅ | composes: "block.path"; |
Mixin-Style class and state composition. Apply other Blocks' Styles to one of yours. |
| Functions | ||
| ✅ | resolve("block.path"); |
Provide an explicit resolution for a given property against another Block. |
| ❌ | constrain(val1, val2 ... valN); |
Constrain this property to a list of specific values that may be set when this Block is extended. |
| ❌ | range(min, max); |
Constrain this property to a range of values that may be set when this Block is extended. |
A "Block" is an isolated stylesheet, written in its own file, that contains all rulesets for any elements, and their various modes and interaction states, for a discrete unit of styling – like a component or design pattern.
Typically, a single Block will contain styles for a particular component or concept, but it is entirely natural – and encouraged – for a template to consume multiple blocks and compose them together in the markup.
A Block file may contain:
The scope ruleset contains styles applied to the root of the scoped style subtree. All other elements assigned styles from a Block must be contained in the document subtree of an element assigned to the block's :scope. We use the special :scope pseudo-class to represent these styles.
The :scope selector may contain the special block-name property so you may provide your own Block name for easy debugging and BEM class generation. If no block-name is provided, we will infer the Block name from the file name.
💡 Feature Note: Block Names
If two Blocks in your project have the same name, CSS Blocks will automatically generate a unique, but still human-readable, name for BEM output mode.
:scope {
block-name: custom-block-name;
/* 👆 optional! */
/* ... more styles ... */
}
Blocks may can contain other classes that may be applied to elements inside the scoped style sub-tree. These are just class selectors, but they are local to that Block and isolated from all other similarly named classes in other Blocks.
.sub-element { /* ... */ }
.other-sub-element { /* ... */ }
Together, the :scope selector and all declared .class selectors define the full interface of stylable elements available to a Block's consumer.
States represent a mode or interaction state that the :scope or a class – called the state's originating element – may be in. States are written as attribute selectors with the special state namespace.
:scope { /* ... */ }
:scope[enabled] { /* ... */ }
.sub-element { /* ... */ }
.sub-element[is-active] { /* ... */ }
⁉️ What the pipe is going on here?
Once upon a time, developers fell in love with XML and thus was born xhtml, a flavor of HTML that allowed HTML elements to be mixed together with elements from other XML syntaxes like SVG and MathML. CSS went along for the ride and so, while many have never seen or used the feature, CSS has support for namespaced elements and attributes. In CSS, the
|symbol is used to delimit between a namespace identifier (assigned by the@namespaceat-rule) and the element or attribute name (also called a qualified name).In markup, instead of a pipe symbol, the colon is used to delimit a namespace identifier and a qualified name. Yes, this is confusing -- but we don't make CSS syntax, we just use it.
States on the :scope selector or a class selector may contain sub-states for more granular styling. Sub-states of a State are mutually exclusive and an element may only be in one sub-state of that state at any given time.
:scope { /* ... */ }
:scope[theme="inverse"] { /* ... */ }
.sub-element { /* ... */ }
/* Applied for *any* value of `color`, including no value. */
.sub-element[color] { /* ... */ }
/* Applied for *specific* values of `color */
.sub-element[color="red"] { /* ... */ }
.sub-element[color="blue"] { /* ... */ }
.sub-element[color="yellow"] { /* ... */ }
CSS Blocks implements a strict subset of CSS. This means we've intentionally restricted some of the features you're allowed to use in a Block file to ensure we can optimize your stylesheets as much as possible!
As Opticss improves, we may choose to loosen some of these restrictions – keep an eye out for syntax updates as we approach the
v1.0.0release!
::before, ::after, and all other pseudo-elements:hover, :active, and all other pseudo-classes,@media, @breakpoint, and all other @at-rules!important is forbidden – you won't be needing it!tag, non-state [attribute], #id and * selectors are forbidden (for now!):matches(), :not(), :something() and :has() are forbidden (for now!)In css-blocks, shallow selectors mean:
/* ✅ Allowed! */
:scope:hover > .my-class { /* ... */ }
/* ❌ Illegal! */
:scope:hover > .my-class + .my-class { /* ... */ }
:scope states, sub-states, or pseudo-classes./* ✅ Allowed! */
:scope:hover .my-class { /* ... */ }
:scope[active] > .my-class { /* ... */ }
:scope[color=red] .my-class { /* ... */ }
/* ❌ Illegal! */
.container:hover > .my-class { /* ... */ }
.container[active] .my-class { /* ... */ }
.container[color=red] .my-class { /* ... */ }
:scope used in the key selector./* ✅ Allowed! */
.my-class + .my-class { /* ... */ }
.my-class:hover ~ .my-class { /* ... */ }
.my-class[active] + .my-class { /* ... */ }
/* ❌ Illegal! */
:scope + .my-class { /* ... */ }
.another-class:hover ~ .my-class { /* ... */ }
.another-class[active] + .my-class { /* ... */ }
💡 Feature Note: Global States and Selectors
"Global States" have their own rules on how they can be used in Block selectors! Keep an eye out for
$ claude mcp add css-blocks \
-- python -m otcore.mcp_server <graph>