bem-wind 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/README.md +215 -0
- package/bin/bem-wind.js +106 -0
- package/dist/generate/index.d.ts +6 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +49 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate/templates.d.ts +3 -0
- package/dist/generate/templates.d.ts.map +1 -0
- package/dist/generate/templates.js +80 -0
- package/dist/generate/templates.js.map +1 -0
- package/dist/generate/utils.d.ts +2 -0
- package/dist/generate/utils.d.ts.map +1 -0
- package/dist/generate/utils.js +24 -0
- package/dist/generate/utils.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/lint/index.d.ts +5 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +38 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/setup/generators.d.ts +11 -0
- package/dist/setup/generators.d.ts.map +1 -0
- package/dist/setup/generators.js +101 -0
- package/dist/setup/generators.js.map +1 -0
- package/dist/setup/index.d.ts +8 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +100 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/templates.d.ts +10 -0
- package/dist/setup/templates.d.ts.map +1 -0
- package/dist/setup/templates.js +370 -0
- package/dist/setup/templates.js.map +1 -0
- package/package.json +65 -0
- package/scripts/sync-skill.js +22 -0
- package/skill/bem-wind/SKILL.md +106 -0
- package/skill/bem-wind/reference/examples.md +206 -0
- package/skill/bem-wind/reference/tokens.css +134 -0
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bem-wind",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to setup BEM-Wind design system with Tailwind, SCSS, and linting rules",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bem-wind": "./bin/bem-wind.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"test": "npm run build && jest --passWithNoTests",
|
|
13
|
+
"sync-skill": "node scripts/sync-skill.js",
|
|
14
|
+
"prepack": "npm run build && npm run sync-skill",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"publish:beta": "npm publish --tag beta"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"bem",
|
|
20
|
+
"tailwind",
|
|
21
|
+
"design-system",
|
|
22
|
+
"scss",
|
|
23
|
+
"linting",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"author": "Taryn Southern",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/tarynstark/bem-wind.git",
|
|
31
|
+
"directory": "tools/bem-wind-cli"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/tarynstark/bem-wind#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/tarynstark/bem-wind/issues"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^11.1.0",
|
|
39
|
+
"inquirer": "^9.2.12",
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"ora": "^7.0.1",
|
|
42
|
+
"fs-extra": "^11.1.1",
|
|
43
|
+
"mustache": "^4.2.0",
|
|
44
|
+
"semver": "^7.5.4"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.8.0",
|
|
48
|
+
"@types/inquirer": "^9.0.3",
|
|
49
|
+
"@types/fs-extra": "^11.0.2",
|
|
50
|
+
"@types/mustache": "^4.2.2",
|
|
51
|
+
"typescript": "^5.2.2",
|
|
52
|
+
"jest": "^29.7.0",
|
|
53
|
+
"@types/jest": "^29.5.5"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=16.0.0"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist/**/*",
|
|
60
|
+
"templates/**/*",
|
|
61
|
+
"bin/**/*",
|
|
62
|
+
"scripts/**/*",
|
|
63
|
+
"skill/**/*"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sync the source-of-truth Claude skill into this package so it ships in the
|
|
4
|
+
* npm tarball. Runs automatically on `prepack` (before `npm pack`/`npm publish`).
|
|
5
|
+
*
|
|
6
|
+
* Source of truth: <repo>/skill/bem-wind (edit there, never in the copy below)
|
|
7
|
+
* Bundled copy: tools/bem-wind-cli/skill/bem-wind (gitignored, generated)
|
|
8
|
+
*/
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const source = path.resolve(__dirname, '..', '..', '..', 'skill', 'bem-wind');
|
|
13
|
+
const dest = path.resolve(__dirname, '..', 'skill', 'bem-wind');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(source)) {
|
|
16
|
+
console.error(`[sync-skill] source skill not found at ${source}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fs.rmSync(path.dirname(dest), { recursive: true, force: true });
|
|
21
|
+
fs.cpSync(source, dest, { recursive: true });
|
|
22
|
+
console.log(`[sync-skill] copied skill → ${path.relative(process.cwd(), dest)}`);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bem-wind
|
|
3
|
+
description: Write, refactor, and review UI CSS using the BEM-Wind methodology — semantic BEM component classes built entirely from Tailwind utilities inside `@apply` directives, with a project prefix and design tokens. Use whenever styling a component, converting utility-heavy / inline-Tailwind HTML into named classes, setting up a design-token system, or reviewing CSS for design-system consistency. Triggers include "style this component", "convert these Tailwind classes", "clean up this CSS", "BEM-Wind", "@apply", "design tokens", "make this consistent".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BEM-Wind
|
|
7
|
+
|
|
8
|
+
BEM-Wind is a CSS methodology that keeps Tailwind's design system while removing utility soup from your markup. You write **semantic, prefixed BEM class names** in HTML and define them in CSS using **only `@apply` directives** built from standard Tailwind utilities. Markup reads like the design; styling stays in the stylesheet; the whole system stays consistent because every value comes from Tailwind's scale (or named design tokens).
|
|
9
|
+
|
|
10
|
+
It is design-system-agnostic: one structural methodology, any visual identity, swapped via a theme layer.
|
|
11
|
+
|
|
12
|
+
## The five rules
|
|
13
|
+
|
|
14
|
+
1. **Everything inside `@apply`.** Never write a raw CSS property (`padding: 12px`) in a component. Compose from Tailwind utilities inside `@apply`. The only exceptions are `content:` for pseudo-elements and binding a CSS custom property (`border-radius: var(--ds-radius-button)`) in a theme.
|
|
15
|
+
2. **Prefix every class** with a 2–4 character project prefix so components are instantly identifiable and searchable. Pick one per project (e.g. `ds-`, `app-`, `acme-`). This skill uses `ds-` in examples — **replace it with the project's actual prefix**, inferring it from existing classes in the codebase before introducing a new one.
|
|
16
|
+
3. **BEM naming:** `[prefix]-block__element--modifier`. Block = component (`ds-card`), element = part of it (`ds-card__title`), modifier = variant/state (`ds-card--featured`). Nest elements/modifiers under the block with SCSS `&`.
|
|
17
|
+
4. **Prefer standard Tailwind values.** Use the scale (`p-4`, `rounded-lg`, `bg-blue-500`, `text-lg`). Reach for an arbitrary bracket value (`max-w-[1200px]`, `bg-[#f7f0e9]`) **only when the design genuinely requires a value off the scale** — a brand color, an exact asset dimension, a specific container width. When you do, it still lives inside `@apply`, never as a standalone property and never as an inline `style=`.
|
|
18
|
+
5. **No utilities in markup.** HTML/JSX carries semantic classes only — never `class="flex items-center p-4 hover:bg-gray-200"`. All of that moves into the `@apply` block.
|
|
19
|
+
|
|
20
|
+
### Hard prohibitions
|
|
21
|
+
- ❌ Standalone CSS properties inside a component (`display: flex`, `box-shadow: …`) — use `@apply flex`, `@apply shadow-md`.
|
|
22
|
+
- ❌ Decimal off-scale utilities: `p-2.5`, `mb-1.5`, `-translate-y-0.5`. Snap to the nearest scale step (`p-2`/`p-3`, `-translate-y-1`).
|
|
23
|
+
- ❌ Inline `style="…"` as an escape hatch — use an arbitrary value inside `@apply` instead.
|
|
24
|
+
- ❌ Utility classes in HTML/JSX.
|
|
25
|
+
- ❌ Inconsistent or missing prefix.
|
|
26
|
+
|
|
27
|
+
## Authoring a component
|
|
28
|
+
|
|
29
|
+
Structure every component block, then its elements, then its modifiers. Always include interactive states (`hover:`, `active:`, `focus:`) and a transition for anything interactive — accessibility and polish are part of the methodology, not an afterthought.
|
|
30
|
+
|
|
31
|
+
```scss
|
|
32
|
+
.ds-button {
|
|
33
|
+
@apply inline-flex items-center justify-center gap-2 cursor-pointer;
|
|
34
|
+
@apply px-4 py-2 text-base font-medium rounded-lg border border-transparent;
|
|
35
|
+
@apply transition-colors duration-200;
|
|
36
|
+
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2;
|
|
37
|
+
|
|
38
|
+
&__icon {
|
|
39
|
+
@apply h-4 w-4 shrink-0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&--primary {
|
|
43
|
+
@apply bg-blue-500 text-white;
|
|
44
|
+
@apply hover:bg-blue-600 active:bg-blue-700;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&--secondary {
|
|
48
|
+
@apply bg-gray-200 text-gray-900 border-gray-300;
|
|
49
|
+
@apply hover:bg-gray-300 active:bg-gray-400;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&--large {
|
|
53
|
+
@apply px-6 py-3 text-lg;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<button class="ds-button ds-button--primary ds-button--large">
|
|
60
|
+
<svg class="ds-button__icon">…</svg>
|
|
61
|
+
Get started
|
|
62
|
+
</button>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
See `reference/examples.md` for card, input, dropdown, modal, tooltip, responsive-grid, animation, and pseudo-element patterns.
|
|
66
|
+
|
|
67
|
+
## Converting utility-heavy HTML
|
|
68
|
+
|
|
69
|
+
When refactoring inline Tailwind into BEM-Wind:
|
|
70
|
+
|
|
71
|
+
1. **Name the component** semantically by purpose, not appearance (`ds-vehicle-card`, not `ds-rounded-box`). Name elements by role (`__title`, `__media`, `__actions`).
|
|
72
|
+
2. **Move every utility** from the markup into the component's `@apply` block, grouped logically (layout → box → color → states). Preserve exact visual output — this is a lift-and-rename, not a redesign.
|
|
73
|
+
3. **Replace the markup** with semantic classes only.
|
|
74
|
+
4. **Lift repeated arbitrary values** (a brand hex used 5×) into a design token / CSS variable and reference it via the theme.
|
|
75
|
+
5. **Migrate gradually** on existing codebases — convert component-by-component, keep visual parity, don't rewrite the design.
|
|
76
|
+
|
|
77
|
+
## Design tokens & theming
|
|
78
|
+
|
|
79
|
+
Values map directly between Figma and code: Figma `spacing/large` → token `--ds-space-large` → used via the Tailwind scale or a utility. Keep **structure** (the component's `@apply` rules) separate from **identity** (colors, fonts, radii) so one component library can wear many brands.
|
|
80
|
+
|
|
81
|
+
- **Core layer** — unstyled structural primitives (`@apply` for layout/spacing/states), no brand colors.
|
|
82
|
+
- **Theme layer** — overrides CSS custom properties (`--ds-color-primary`, `--ds-radius-button`, `--ds-font-display`) and binds them where a token is genuinely dynamic.
|
|
83
|
+
|
|
84
|
+
`reference/tokens.css` is a complete, ready-to-drop-in token scaffold (spacing, type scale, containers, radius, shadow, z-index, motion, breakpoints, color placeholders, fonts). Copy it, reprefix it, and override the color/font placeholders per project theme.
|
|
85
|
+
|
|
86
|
+
## Review checklist
|
|
87
|
+
|
|
88
|
+
When reviewing CSS for BEM-Wind compliance, verify:
|
|
89
|
+
|
|
90
|
+
- [ ] Every property is inside `@apply` (only `content:` / CSS-var binding excepted)
|
|
91
|
+
- [ ] Consistent project prefix on every class
|
|
92
|
+
- [ ] Correct BEM: `[prefix]-block__element--modifier`, nested with `&`
|
|
93
|
+
- [ ] Standard Tailwind values; arbitrary brackets only where the design truly needs them, and only inside `@apply`
|
|
94
|
+
- [ ] No decimal off-scale utilities (`p-2.5`, `translate-y-0.5`)
|
|
95
|
+
- [ ] Interactive elements have hover/active/focus states + a transition
|
|
96
|
+
- [ ] No utility classes or inline `style=` in the markup
|
|
97
|
+
- [ ] Semantic names describe purpose, not appearance
|
|
98
|
+
- [ ] Primitives are structural; brand color/font/radius live in the theme layer
|
|
99
|
+
|
|
100
|
+
## Decision flow for any value
|
|
101
|
+
|
|
102
|
+
1. Is there a standard Tailwind utility? → use it.
|
|
103
|
+
2. Is it near a scale step? → snap to the nearest step.
|
|
104
|
+
3. Does the design genuinely demand an off-scale value? → arbitrary bracket value, inside `@apply`.
|
|
105
|
+
4. Is this value reused or brand-defining? → promote it to a design token in the theme.
|
|
106
|
+
5. Interactive? → add hover/active/focus + transition.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# BEM-Wind component patterns
|
|
2
|
+
|
|
3
|
+
Reference implementations. Every example uses the `ds-` prefix as a placeholder — **swap in the project's real 2–4 char prefix**. All styling lives inside `@apply`; markup carries semantic classes only.
|
|
4
|
+
|
|
5
|
+
## Card
|
|
6
|
+
|
|
7
|
+
```scss
|
|
8
|
+
.ds-card {
|
|
9
|
+
@apply flex flex-col bg-white rounded-xl border border-gray-200 shadow-sm;
|
|
10
|
+
@apply transition-shadow duration-200 hover:shadow-md;
|
|
11
|
+
|
|
12
|
+
&__media {
|
|
13
|
+
@apply aspect-video w-full object-cover rounded-t-xl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&__body {
|
|
17
|
+
@apply flex flex-col gap-2 p-6;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__title {
|
|
21
|
+
@apply text-lg font-semibold text-gray-900;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&__text {
|
|
25
|
+
@apply text-sm text-gray-600 leading-relaxed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&__footer {
|
|
29
|
+
@apply mt-auto flex items-center justify-between p-6 border-t border-gray-200;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&--featured {
|
|
33
|
+
@apply border-blue-500 ring-1 ring-blue-500;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<article class="ds-card ds-card--featured">
|
|
40
|
+
<img class="ds-card__media" src="…" alt="…" />
|
|
41
|
+
<div class="ds-card__body">
|
|
42
|
+
<h3 class="ds-card__title">Title</h3>
|
|
43
|
+
<p class="ds-card__text">Supporting copy.</p>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="ds-card__footer">…</div>
|
|
46
|
+
</article>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Form input
|
|
50
|
+
|
|
51
|
+
```scss
|
|
52
|
+
.ds-input {
|
|
53
|
+
@apply w-full px-3 py-2 bg-white text-gray-900 placeholder-gray-400;
|
|
54
|
+
@apply border border-gray-300 rounded-lg;
|
|
55
|
+
@apply transition-colors duration-200;
|
|
56
|
+
@apply hover:border-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none;
|
|
57
|
+
|
|
58
|
+
&--error {
|
|
59
|
+
@apply border-red-500 focus:border-red-500 focus:ring-red-500/20;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&:disabled {
|
|
63
|
+
@apply bg-gray-100 text-gray-400 cursor-not-allowed;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Dropdown
|
|
69
|
+
|
|
70
|
+
```scss
|
|
71
|
+
.ds-dropdown {
|
|
72
|
+
@apply relative inline-block;
|
|
73
|
+
|
|
74
|
+
&__trigger {
|
|
75
|
+
@apply inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-300 bg-white;
|
|
76
|
+
@apply hover:border-gray-400 transition-colors duration-200;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&__menu {
|
|
80
|
+
@apply absolute top-full left-0 mt-1 min-w-[12rem] py-1;
|
|
81
|
+
@apply bg-white rounded-lg border border-gray-200 shadow-lg;
|
|
82
|
+
@apply invisible opacity-0 transition-opacity duration-150;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&__item {
|
|
86
|
+
@apply flex items-center gap-2 px-3 py-2 text-sm text-gray-700;
|
|
87
|
+
@apply hover:bg-gray-100 active:bg-gray-200 cursor-pointer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&--open &__menu {
|
|
91
|
+
@apply visible opacity-100;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Modal
|
|
97
|
+
|
|
98
|
+
```scss
|
|
99
|
+
.ds-modal {
|
|
100
|
+
@apply fixed inset-0 z-50 flex items-center justify-center p-4;
|
|
101
|
+
|
|
102
|
+
&__overlay {
|
|
103
|
+
@apply absolute inset-0 bg-black/50;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&__panel {
|
|
107
|
+
@apply relative z-10 w-full max-w-lg max-h-[90vh] overflow-y-auto;
|
|
108
|
+
@apply bg-white rounded-2xl shadow-2xl;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&__header {
|
|
112
|
+
@apply flex items-center justify-between p-6 border-b border-gray-200;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&__body {
|
|
116
|
+
@apply p-6;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&__footer {
|
|
120
|
+
@apply flex justify-end gap-3 p-6 border-t border-gray-200;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Responsive grid (mobile-first)
|
|
126
|
+
|
|
127
|
+
```scss
|
|
128
|
+
.ds-grid {
|
|
129
|
+
@apply grid grid-cols-1 gap-4;
|
|
130
|
+
@apply sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Animation / loader
|
|
135
|
+
|
|
136
|
+
```scss
|
|
137
|
+
.ds-loader {
|
|
138
|
+
@apply h-6 w-6 rounded-full border-2 border-gray-200 border-t-blue-500;
|
|
139
|
+
@apply animate-spin;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Pseudo-element (tooltip) — the `content:` exception
|
|
144
|
+
|
|
145
|
+
`content:` is one of the few raw properties allowed, since Tailwind has no `@apply` for it.
|
|
146
|
+
|
|
147
|
+
```scss
|
|
148
|
+
.ds-tooltip {
|
|
149
|
+
@apply relative;
|
|
150
|
+
|
|
151
|
+
&::before {
|
|
152
|
+
@apply absolute bottom-full left-1/2 mb-2 -translate-x-1/2;
|
|
153
|
+
@apply px-2 py-1 text-xs text-white bg-gray-900 rounded whitespace-nowrap;
|
|
154
|
+
@apply invisible opacity-0 transition-opacity duration-150;
|
|
155
|
+
content: attr(data-tooltip);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&:hover::before {
|
|
159
|
+
@apply visible opacity-100;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Gradients & shadows — keep them in Tailwind
|
|
165
|
+
|
|
166
|
+
```scss
|
|
167
|
+
/* ❌ Don't drop to raw CSS */
|
|
168
|
+
.ds-banner {
|
|
169
|
+
background: linear-gradient(45deg, #ff0000, #00ff00);
|
|
170
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ✅ Compose from Tailwind utilities */
|
|
174
|
+
.ds-banner {
|
|
175
|
+
@apply bg-gradient-to-r from-red-500 to-green-500 shadow-lg;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Theme override — structure vs. identity
|
|
180
|
+
|
|
181
|
+
Core primitive (structural, brand-agnostic):
|
|
182
|
+
|
|
183
|
+
```scss
|
|
184
|
+
.ds-button {
|
|
185
|
+
@apply inline-flex items-center justify-center px-4 py-2 rounded-lg;
|
|
186
|
+
@apply transition-colors duration-200;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Theme layer (brand identity via tokens — binding a CSS var is an allowed raw property):
|
|
191
|
+
|
|
192
|
+
```scss
|
|
193
|
+
:root {
|
|
194
|
+
--ds-color-primary: #371a1e; /* burgundy */
|
|
195
|
+
--ds-radius-button: 5rem; /* pill */
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.ds-button {
|
|
199
|
+
border-radius: var(--ds-radius-button);
|
|
200
|
+
|
|
201
|
+
&--primary {
|
|
202
|
+
@apply text-white;
|
|
203
|
+
background-color: var(--ds-color-primary);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BEM-Wind core design tokens (public scaffold)
|
|
3
|
+
*
|
|
4
|
+
* Drop-in starting point. Find/replace the `ds-` prefix with your project's
|
|
5
|
+
* prefix, then override the COLOR and FONT placeholders in a per-project theme.
|
|
6
|
+
* Spacing / type / radius mirror Tailwind's scale so `@apply` values and tokens
|
|
7
|
+
* stay in lockstep.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
/* ===== SPACING (maps to Figma spacing tokens) ===== */
|
|
12
|
+
--ds-space-0: 0;
|
|
13
|
+
--ds-space-1: 0.25rem; /* 4px */
|
|
14
|
+
--ds-space-2: 0.5rem; /* 8px */
|
|
15
|
+
--ds-space-3: 0.75rem; /* 12px */
|
|
16
|
+
--ds-space-4: 1rem; /* 16px - base */
|
|
17
|
+
--ds-space-5: 1.25rem; /* 20px */
|
|
18
|
+
--ds-space-6: 1.5rem; /* 24px */
|
|
19
|
+
--ds-space-8: 2rem; /* 32px */
|
|
20
|
+
--ds-space-10: 2.5rem; /* 40px */
|
|
21
|
+
--ds-space-12: 3rem; /* 48px */
|
|
22
|
+
--ds-space-16: 4rem; /* 64px */
|
|
23
|
+
--ds-space-20: 5rem; /* 80px */
|
|
24
|
+
--ds-space-24: 6rem; /* 96px */
|
|
25
|
+
|
|
26
|
+
--ds-space-xsmall: var(--ds-space-2);
|
|
27
|
+
--ds-space-small: var(--ds-space-3);
|
|
28
|
+
--ds-space-medium: var(--ds-space-4);
|
|
29
|
+
--ds-space-large: var(--ds-space-6);
|
|
30
|
+
--ds-space-xlarge: var(--ds-space-8);
|
|
31
|
+
--ds-space-xxlarge: var(--ds-space-12);
|
|
32
|
+
|
|
33
|
+
/* ===== TYPOGRAPHY ===== */
|
|
34
|
+
--ds-text-xs: 0.75rem; /* 12px */
|
|
35
|
+
--ds-text-sm: 0.875rem; /* 14px */
|
|
36
|
+
--ds-text-base: 1rem; /* 16px */
|
|
37
|
+
--ds-text-lg: 1.125rem; /* 18px */
|
|
38
|
+
--ds-text-xl: 1.25rem; /* 20px */
|
|
39
|
+
--ds-text-2xl: 1.5rem; /* 24px */
|
|
40
|
+
--ds-text-3xl: 1.875rem; /* 30px */
|
|
41
|
+
--ds-text-4xl: 2.25rem; /* 36px */
|
|
42
|
+
--ds-text-5xl: 3rem; /* 48px */
|
|
43
|
+
--ds-text-6xl: 3.75rem; /* 60px */
|
|
44
|
+
--ds-text-7xl: 4.5rem; /* 72px */
|
|
45
|
+
|
|
46
|
+
--ds-text-display-sm: var(--ds-text-3xl);
|
|
47
|
+
--ds-text-display-md: var(--ds-text-4xl);
|
|
48
|
+
--ds-text-display-lg: var(--ds-text-5xl);
|
|
49
|
+
--ds-text-display-xl: var(--ds-text-6xl);
|
|
50
|
+
|
|
51
|
+
--ds-leading-tight: 1.25;
|
|
52
|
+
--ds-leading-normal: 1.5;
|
|
53
|
+
--ds-leading-relaxed: 1.625;
|
|
54
|
+
|
|
55
|
+
/* ===== CONTAINERS ===== */
|
|
56
|
+
--ds-container-narrow: 42rem; /* 672px */
|
|
57
|
+
--ds-container-content: 56rem; /* 896px */
|
|
58
|
+
--ds-container-wide: 72rem; /* 1152px */
|
|
59
|
+
--ds-container-full: 100%;
|
|
60
|
+
|
|
61
|
+
/* ===== RADIUS ===== */
|
|
62
|
+
--ds-radius-sm: 0.25rem; /* 4px */
|
|
63
|
+
--ds-radius-md: 0.375rem; /* 6px */
|
|
64
|
+
--ds-radius-lg: 0.5rem; /* 8px */
|
|
65
|
+
--ds-radius-xl: 0.75rem; /* 12px */
|
|
66
|
+
--ds-radius-2xl: 1rem; /* 16px */
|
|
67
|
+
--ds-radius-full: 9999px;
|
|
68
|
+
|
|
69
|
+
/* ===== SHADOWS ===== */
|
|
70
|
+
--ds-shadow-sm: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1);
|
|
71
|
+
--ds-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);
|
|
72
|
+
--ds-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
|
73
|
+
--ds-shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);
|
|
74
|
+
|
|
75
|
+
/* ===== Z-INDEX ===== */
|
|
76
|
+
--ds-z-dropdown: 1000;
|
|
77
|
+
--ds-z-sticky: 1020;
|
|
78
|
+
--ds-z-overlay: 1040;
|
|
79
|
+
--ds-z-modal: 1050;
|
|
80
|
+
--ds-z-popover: 1060;
|
|
81
|
+
--ds-z-tooltip: 1070;
|
|
82
|
+
--ds-z-toast: 1080;
|
|
83
|
+
|
|
84
|
+
/* ===== MOTION ===== */
|
|
85
|
+
--ds-duration-fast: 150ms;
|
|
86
|
+
--ds-duration-normal: 300ms;
|
|
87
|
+
--ds-duration-slow: 500ms;
|
|
88
|
+
--ds-ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
89
|
+
--ds-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
90
|
+
|
|
91
|
+
/* ===== BREAKPOINTS ===== */
|
|
92
|
+
--ds-screen-sm: 640px;
|
|
93
|
+
--ds-screen-md: 768px;
|
|
94
|
+
--ds-screen-lg: 1024px;
|
|
95
|
+
--ds-screen-xl: 1280px;
|
|
96
|
+
--ds-screen-2xl: 1536px;
|
|
97
|
+
|
|
98
|
+
/* ===== COLOR (placeholders — OVERRIDE PER THEME) ===== */
|
|
99
|
+
--ds-color-white: #ffffff;
|
|
100
|
+
--ds-color-black: #000000;
|
|
101
|
+
|
|
102
|
+
--ds-color-gray-50: #f9fafb;
|
|
103
|
+
--ds-color-gray-100: #f3f4f6;
|
|
104
|
+
--ds-color-gray-200: #e5e7eb;
|
|
105
|
+
--ds-color-gray-300: #d1d5db;
|
|
106
|
+
--ds-color-gray-400: #9ca3af;
|
|
107
|
+
--ds-color-gray-500: #6b7280;
|
|
108
|
+
--ds-color-gray-600: #4b5563;
|
|
109
|
+
--ds-color-gray-700: #374151;
|
|
110
|
+
--ds-color-gray-800: #1f2937;
|
|
111
|
+
--ds-color-gray-900: #111827;
|
|
112
|
+
|
|
113
|
+
--ds-color-primary: var(--ds-color-gray-900);
|
|
114
|
+
--ds-color-secondary: var(--ds-color-gray-600);
|
|
115
|
+
--ds-color-accent: var(--ds-color-gray-700);
|
|
116
|
+
|
|
117
|
+
--ds-color-text-primary: var(--ds-color-gray-900);
|
|
118
|
+
--ds-color-text-secondary: var(--ds-color-gray-600);
|
|
119
|
+
--ds-color-text-muted: var(--ds-color-gray-400);
|
|
120
|
+
--ds-color-text-inverse: var(--ds-color-white);
|
|
121
|
+
|
|
122
|
+
--ds-color-bg-primary: var(--ds-color-white);
|
|
123
|
+
--ds-color-bg-secondary: var(--ds-color-gray-50);
|
|
124
|
+
--ds-color-border-primary: var(--ds-color-gray-200);
|
|
125
|
+
|
|
126
|
+
/* ===== FONTS (override per theme) ===== */
|
|
127
|
+
--ds-font-sans: ui-sans-serif, system-ui, sans-serif;
|
|
128
|
+
--ds-font-serif: ui-serif, Georgia, "Times New Roman", serif;
|
|
129
|
+
--ds-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
130
|
+
|
|
131
|
+
--ds-font-display: var(--ds-font-serif);
|
|
132
|
+
--ds-font-body: var(--ds-font-sans);
|
|
133
|
+
--ds-font-code: var(--ds-font-mono);
|
|
134
|
+
}
|