@bizrk/leancss 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +125 -0
- package/package.json +42 -0
- package/readme.md +410 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adam Birkner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const leancss = (opts = {}) => {
|
|
4
|
+
return {
|
|
5
|
+
postcssPlugin: 'leancss',
|
|
6
|
+
Once(root, { result }) {
|
|
7
|
+
const sets = new Map();
|
|
8
|
+
// 1. Collect and Validate sets
|
|
9
|
+
root.walkAtRules('set', (atRule) => {
|
|
10
|
+
const name = atRule.params.trim();
|
|
11
|
+
if (!name) {
|
|
12
|
+
atRule.warn(result, 'Missing name for @set');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (sets.has(name)) {
|
|
16
|
+
throw atRule.error(`Duplicate @set definition for "${name}"`);
|
|
17
|
+
}
|
|
18
|
+
let isAlias = false;
|
|
19
|
+
// Validate contents
|
|
20
|
+
atRule.walk((node) => {
|
|
21
|
+
if (node.type === 'atrule') {
|
|
22
|
+
const atNode = node;
|
|
23
|
+
if (atNode.name !== 'lift') {
|
|
24
|
+
throw node.error(`Unsupported at-rule @${atNode.name} inside @set. Only @lift is allowed.`);
|
|
25
|
+
}
|
|
26
|
+
isAlias = true;
|
|
27
|
+
}
|
|
28
|
+
else if (node.type === 'rule') {
|
|
29
|
+
throw node.error(`Nested selectors are not supported inside @set in v1.`);
|
|
30
|
+
}
|
|
31
|
+
else if (node.type !== 'decl' && node.type !== 'comment') {
|
|
32
|
+
throw node.error(`Unsupported node type "${node.type}" inside @set.`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
sets.set(name, {
|
|
36
|
+
name,
|
|
37
|
+
sourceFile: atRule.source?.input.file,
|
|
38
|
+
node: atRule,
|
|
39
|
+
isAlias,
|
|
40
|
+
resolvedDeclarations: [] // Will be populated in resolution phase
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// 3. Resolve aliases
|
|
44
|
+
function resolveSet(name, resolutionChain, originNode) {
|
|
45
|
+
const setDef = sets.get(name);
|
|
46
|
+
if (!setDef) {
|
|
47
|
+
throw originNode.error(`Unknown set "${name}" referenced in @lift`);
|
|
48
|
+
}
|
|
49
|
+
// Cache hit
|
|
50
|
+
if (setDef.resolvedDeclarations.length > 0) {
|
|
51
|
+
return setDef.resolvedDeclarations;
|
|
52
|
+
}
|
|
53
|
+
// It might be empty, let's distinguish between empty resolution and unresolved by adding a flag or just always resolving.
|
|
54
|
+
// Circular detection
|
|
55
|
+
if (resolutionChain.includes(name)) {
|
|
56
|
+
const chain = [...resolutionChain, name].join(' -> ');
|
|
57
|
+
throw originNode.error(`Circular alias reference detected: ${chain}`);
|
|
58
|
+
}
|
|
59
|
+
const currentChain = [...resolutionChain, name];
|
|
60
|
+
const resolved = [];
|
|
61
|
+
setDef.node.walk((node) => {
|
|
62
|
+
if (node.type === 'decl') {
|
|
63
|
+
resolved.push(node.clone());
|
|
64
|
+
}
|
|
65
|
+
else if (node.type === 'atrule') {
|
|
66
|
+
const atNode = node;
|
|
67
|
+
if (atNode.name === 'lift') {
|
|
68
|
+
const refs = atNode.params.split(/\s+/).filter(Boolean);
|
|
69
|
+
for (const ref of refs) {
|
|
70
|
+
const expanded = resolveSet(ref, currentChain, atNode);
|
|
71
|
+
for (const decl of expanded) {
|
|
72
|
+
resolved.push(decl.clone());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
setDef.resolvedDeclarations = resolved;
|
|
79
|
+
return resolved;
|
|
80
|
+
}
|
|
81
|
+
// Resolve all sets eagerly (or could be done lazily when expanding selector-level @lift)
|
|
82
|
+
for (const [name, setDef] of sets.entries()) {
|
|
83
|
+
if (setDef.resolvedDeclarations.length === 0) {
|
|
84
|
+
resolveSet(name, [], setDef.node);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 4. Expand selector-level @lift
|
|
88
|
+
root.walkAtRules('lift', (atRule) => {
|
|
89
|
+
// Ignore @lift inside @set
|
|
90
|
+
let parent = atRule.parent;
|
|
91
|
+
let inSet = false;
|
|
92
|
+
while (parent) {
|
|
93
|
+
if (parent.type === 'atrule' && parent.name === 'set') {
|
|
94
|
+
inSet = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
parent = parent.parent;
|
|
98
|
+
}
|
|
99
|
+
if (inSet)
|
|
100
|
+
return;
|
|
101
|
+
if (atRule.parent?.type !== 'rule') {
|
|
102
|
+
throw atRule.error(`@lift is only allowed inside standard rules or @set blocks in v1.`);
|
|
103
|
+
}
|
|
104
|
+
const refs = atRule.params.split(/\s+/).filter(Boolean);
|
|
105
|
+
const declsToInsert = [];
|
|
106
|
+
for (const ref of refs) {
|
|
107
|
+
const setDef = sets.get(ref);
|
|
108
|
+
if (!setDef) {
|
|
109
|
+
throw atRule.error(`Unknown set "${ref}" referenced in @lift`);
|
|
110
|
+
}
|
|
111
|
+
for (const decl of setDef.resolvedDeclarations) {
|
|
112
|
+
declsToInsert.push(decl.clone());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
atRule.replaceWith(...declsToInsert);
|
|
116
|
+
});
|
|
117
|
+
// 5. Remove @set definitions
|
|
118
|
+
root.walkAtRules('set', (atRule) => {
|
|
119
|
+
atRule.remove();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
leancss.postcss = true;
|
|
125
|
+
exports.default = leancss;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bizrk/leancss",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CSS-first utility composition with @set and @lift that provides a clean, layer-aware alternative to Sass mixins and Tailwind @apply for atomic compositions.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"directories": {
|
|
8
|
+
"test": "tests"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"css",
|
|
17
|
+
"postcss",
|
|
18
|
+
"postcss-plugin",
|
|
19
|
+
"utility",
|
|
20
|
+
"atomic",
|
|
21
|
+
"composition",
|
|
22
|
+
"design-system",
|
|
23
|
+
"leancss",
|
|
24
|
+
"set",
|
|
25
|
+
"lift"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"readme.md"
|
|
30
|
+
],
|
|
31
|
+
"author": "Adam Birkner",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"postcss": "^8.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"postcss": "^8.4.38",
|
|
38
|
+
"@types/node": "^20.12.7",
|
|
39
|
+
"typescript": "^5.4.5",
|
|
40
|
+
"vitest": "^1.5.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# LeanCSS
|
|
2
|
+
|
|
3
|
+
**Define sets. Lift them into components. Ship lean CSS.**
|
|
4
|
+
|
|
5
|
+
LeanCSS is a CSS-first utility composition tool.
|
|
6
|
+
|
|
7
|
+
It lets you define reusable style bundles with `@set`, then expand them inside selectors using `@lift`.
|
|
8
|
+
|
|
9
|
+
Unlike utility-class frameworks, LeanCSS keeps styling in CSS instead of HTML while still enabling composable, reusable patterns.
|
|
10
|
+
|
|
11
|
+
LeanCSS runs at build time and outputs plain CSS.
|
|
12
|
+
|
|
13
|
+
* * *
|
|
14
|
+
|
|
15
|
+
# Why LeanCSS?
|
|
16
|
+
|
|
17
|
+
Modern CSS tooling tends to fall into two camps.
|
|
18
|
+
|
|
19
|
+
### Utility frameworks
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
|
|
23
|
+
<button class="inline-flex items-center gap-2 px-4 py-2 rounded-md">
|
|
24
|
+
|
|
25
|
+
What is this?
|
|
26
|
+
|
|
27
|
+
Pros
|
|
28
|
+
|
|
29
|
+
- fast to prototype
|
|
30
|
+
|
|
31
|
+
- composable
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Cons
|
|
35
|
+
|
|
36
|
+
- styles move into markup
|
|
37
|
+
|
|
38
|
+
- semantics become unclear
|
|
39
|
+
|
|
40
|
+
- component styles become fragmented
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
* * *
|
|
44
|
+
|
|
45
|
+
### Sass mixins
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
|
|
49
|
+
.button {
|
|
50
|
+
@include flex-center;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Pros
|
|
54
|
+
|
|
55
|
+
- reusable abstractions
|
|
56
|
+
|
|
57
|
+
Cons
|
|
58
|
+
|
|
59
|
+
- hidden logic
|
|
60
|
+
|
|
61
|
+
- hard to inspect
|
|
62
|
+
|
|
63
|
+
- often grows into a mini programming language
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
* * *
|
|
67
|
+
|
|
68
|
+
### LeanCSS approach
|
|
69
|
+
|
|
70
|
+
LeanCSS keeps composition **inside CSS itself**.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
|
|
74
|
+
@set inline-flex {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
}
|
|
77
|
+
<br/>@set items-center {
|
|
78
|
+
align-items: center;
|
|
79
|
+
}
|
|
80
|
+
<br/>@set gap-2 {
|
|
81
|
+
gap: var(--space-2);
|
|
82
|
+
}
|
|
83
|
+
<br/>.button {
|
|
84
|
+
@lift inline-flex items-center gap-2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Output:
|
|
88
|
+
|
|
89
|
+
.button {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: var(--space-2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
* * *
|
|
96
|
+
|
|
97
|
+
# Features
|
|
98
|
+
|
|
99
|
+
LeanCSS is intentionally simple.
|
|
100
|
+
|
|
101
|
+
• CSS-first authoring
|
|
102
|
+
• reusable style bundles via `@set`
|
|
103
|
+
• composition via `@lift`
|
|
104
|
+
• alias sets
|
|
105
|
+
• cascade layer friendly
|
|
106
|
+
• works with `.css` and `.scss`
|
|
107
|
+
• outputs plain CSS
|
|
108
|
+
• zero runtime
|
|
109
|
+
|
|
110
|
+
* * *
|
|
111
|
+
|
|
112
|
+
# Installation
|
|
113
|
+
|
|
114
|
+
npm install @bizrk/leancss
|
|
115
|
+
|
|
116
|
+
Add LeanCSS to your PostCSS configuration.
|
|
117
|
+
|
|
118
|
+
Example:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
import leancss from "@bizrk/leancss"
|
|
122
|
+
```
|
|
123
|
+
export default {
|
|
124
|
+
plugins: \[
|
|
125
|
+
leancss()
|
|
126
|
+
\]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
LeanCSS runs during the CSS build process.
|
|
130
|
+
|
|
131
|
+
* * *
|
|
132
|
+
|
|
133
|
+
# Editor Support (VS Code)
|
|
134
|
+
|
|
135
|
+
To prevent VS Code from reporting "Unknown at-rule" warnings for `@set` and `@lift`, you can configure custom CSS data.
|
|
136
|
+
|
|
137
|
+
Create `.vscode/leancss.css-data.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"version": 1.1,
|
|
142
|
+
"atDirectives": [
|
|
143
|
+
{
|
|
144
|
+
"name": "@set",
|
|
145
|
+
"description": "LeanCSS: Defines a reusable declaration bundle or alias bundle."
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "@lift",
|
|
149
|
+
"description": "LeanCSS: Expands one or more sets into the current selector rule."
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Then tell VS Code to use it in `.vscode/settings.json`:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"css.customData": ["./.vscode/leancss.css-data.json"],
|
|
160
|
+
"scss.customData": ["./.vscode/leancss.css-data.json"]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
* * *
|
|
165
|
+
|
|
166
|
+
# Basic Usage
|
|
167
|
+
|
|
168
|
+
Define reusable sets.
|
|
169
|
+
|
|
170
|
+
@set inline-flex {
|
|
171
|
+
display: inline-flex;
|
|
172
|
+
}
|
|
173
|
+
<br/>@set items-center {
|
|
174
|
+
align-items: center;
|
|
175
|
+
}
|
|
176
|
+
<br/>@set gap-2 {
|
|
177
|
+
gap: var(--space-2);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Use them in selectors.
|
|
181
|
+
|
|
182
|
+
.button {
|
|
183
|
+
@lift inline-flex items-center gap-2;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
LeanCSS expands the sets during compilation.
|
|
187
|
+
|
|
188
|
+
* * *
|
|
189
|
+
|
|
190
|
+
# Alias Sets
|
|
191
|
+
|
|
192
|
+
Sets can compose other sets.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
|
|
196
|
+
@set flex-center {
|
|
197
|
+
@lift inline-flex items-center justify-center;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Usage:
|
|
201
|
+
|
|
202
|
+
.icon {
|
|
203
|
+
@lift flex-center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Output:
|
|
207
|
+
|
|
208
|
+
.icon {
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
* * *
|
|
215
|
+
|
|
216
|
+
# Cascade Layers
|
|
217
|
+
|
|
218
|
+
LeanCSS works well with CSS cascade layers.
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
|
|
222
|
+
@layer reset, tokens, base, utilities, components, overrides;
|
|
223
|
+
|
|
224
|
+
Define sets in a utilities layer:
|
|
225
|
+
|
|
226
|
+
@layer utilities {
|
|
227
|
+
@set inline-flex {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
}
|
|
230
|
+
<br/>@set items-center {
|
|
231
|
+
align-items: center;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Consume them in components:
|
|
236
|
+
|
|
237
|
+
@layer components {
|
|
238
|
+
.button {
|
|
239
|
+
@lift inline-flex items-center;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
LeanCSS preserves layer ordering.
|
|
244
|
+
|
|
245
|
+
* * *
|
|
246
|
+
|
|
247
|
+
# Example Project Structure
|
|
248
|
+
|
|
249
|
+
src/styles
|
|
250
|
+
├─ tokens.css
|
|
251
|
+
├─ utilities.css
|
|
252
|
+
├─ base.css
|
|
253
|
+
├─ components
|
|
254
|
+
│ ├─ button.css
|
|
255
|
+
│ ├─ card.css
|
|
256
|
+
│ └─ hero.css
|
|
257
|
+
└─ app.css
|
|
258
|
+
|
|
259
|
+
Example utilities file:
|
|
260
|
+
|
|
261
|
+
```css
|
|
262
|
+
@layer utilities {
|
|
263
|
+
@set inline-flex {
|
|
264
|
+
display: inline-flex;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@set items-center {
|
|
268
|
+
align-items: center;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@set gap-2 {
|
|
272
|
+
gap: var(--space-2);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@set rounded-md {
|
|
276
|
+
border-radius: var(--radius-md);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Example component:
|
|
282
|
+
|
|
283
|
+
```css
|
|
284
|
+
.button {
|
|
285
|
+
@lift inline-flex items-center gap-2 rounded-md;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Running PostCSS
|
|
290
|
+
|
|
291
|
+
You can use the `postcss-cli` package to compile your CSS. Add these scripts to your `package.json`:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"scripts": {
|
|
296
|
+
"build:css": "postcss src/styles/app.css -o dist/output.css",
|
|
297
|
+
"watch:css": "postcss src/styles/app.css -o dist/output.css --watch"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Run `npm run build:css` to build your styles once for production, or `npm run watch:css` while actively developing to automatically re-compile whenever you save your CSS files.
|
|
303
|
+
|
|
304
|
+
* * *
|
|
305
|
+
|
|
306
|
+
# Philosophy
|
|
307
|
+
|
|
308
|
+
LeanCSS focuses on small, composable primitives.
|
|
309
|
+
|
|
310
|
+
It encourages:
|
|
311
|
+
|
|
312
|
+
• semantic selectors
|
|
313
|
+
• design token usage
|
|
314
|
+
• cascade layers
|
|
315
|
+
• small reusable patterns
|
|
316
|
+
|
|
317
|
+
It intentionally avoids:
|
|
318
|
+
|
|
319
|
+
• HTML class scanning
|
|
320
|
+
• runtime styling engines
|
|
321
|
+
• heavy configuration files
|
|
322
|
+
|
|
323
|
+
LeanCSS is designed to remain small, predictable, and easy to reason about.
|
|
324
|
+
|
|
325
|
+
* * *
|
|
326
|
+
|
|
327
|
+
# Error Handling
|
|
328
|
+
|
|
329
|
+
LeanCSS validates styles during compilation.
|
|
330
|
+
|
|
331
|
+
### Unknown set
|
|
332
|
+
|
|
333
|
+
.button {
|
|
334
|
+
@lift missing-set;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Error:
|
|
338
|
+
|
|
339
|
+
Unknown LeanCSS set: missing-set
|
|
340
|
+
|
|
341
|
+
* * *
|
|
342
|
+
|
|
343
|
+
### Duplicate set
|
|
344
|
+
|
|
345
|
+
@set panel { ... }
|
|
346
|
+
@set panel
|
|
347
|
+
|
|
348
|
+
Error:
|
|
349
|
+
|
|
350
|
+
Duplicate LeanCSS set definition: panel
|
|
351
|
+
|
|
352
|
+
* * *
|
|
353
|
+
|
|
354
|
+
### Circular reference
|
|
355
|
+
|
|
356
|
+
@set a { @lift b }
|
|
357
|
+
@set b
|
|
358
|
+
|
|
359
|
+
Error:
|
|
360
|
+
|
|
361
|
+
Circular set reference detected
|
|
362
|
+
|
|
363
|
+
* * *
|
|
364
|
+
|
|
365
|
+
# Comparison
|
|
366
|
+
|
|
367
|
+
| Tool | Composition Location |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| Tailwind | HTML |
|
|
370
|
+
| Sass mixins | Sass |
|
|
371
|
+
| LeanCSS | CSS |
|
|
372
|
+
|
|
373
|
+
LeanCSS combines composability with semantic CSS architecture.
|
|
374
|
+
|
|
375
|
+
* * *
|
|
376
|
+
|
|
377
|
+
# License
|
|
378
|
+
|
|
379
|
+
LeanCSS is released under the MIT License.
|
|
380
|
+
|
|
381
|
+
* * *
|
|
382
|
+
|
|
383
|
+
# Contributing
|
|
384
|
+
|
|
385
|
+
Contributions are welcome.
|
|
386
|
+
|
|
387
|
+
Areas that benefit from community help:
|
|
388
|
+
|
|
389
|
+
• integrations
|
|
390
|
+
• preset libraries
|
|
391
|
+
• documentation
|
|
392
|
+
• testing
|
|
393
|
+
|
|
394
|
+
* * *
|
|
395
|
+
|
|
396
|
+
# Future Ideas
|
|
397
|
+
|
|
398
|
+
LeanCSS v1 focuses on a minimal core.
|
|
399
|
+
|
|
400
|
+
Possible future additions include:
|
|
401
|
+
|
|
402
|
+
• preset utility libraries
|
|
403
|
+
• devtools for inspecting `@lift` expansion
|
|
404
|
+
• design system integrations
|
|
405
|
+
|
|
406
|
+
* * *
|
|
407
|
+
|
|
408
|
+
# LeanCSS
|
|
409
|
+
|
|
410
|
+
**Define sets. Lift them into components.**
|