Appearance
@vibe-labs/design — Package Development Guide
How to build a design-level package for the Vibe Design System. Follow this structure exactly — it's what every package uses.
Package Anatomy
vibe-design-{name}/
├── package.json
├── readme.md
├── scripts/
│ └── build.mjs # generates utility CSS from tokens
└── src/
├── index.css # barrel — imports all source CSS
├── {name}.css # token definitions (@layer vibe.tokens)
└── (optional subdirs) # for opt-in modules (scales/, palettes/, etc.)After build:
dist/
├── index.css # barrel (copied from src, then overwritten with correct imports)
├── {name}.css # token definitions (copied from src)
├── {name}.g.css # generated utility classes
└── (optional subdirs) # if the package has opt-in exportspackage.json
Standard (no optional exports)
json
{
"name": "@vibe-labs/design-{name}",
"version": "0.1.0",
"private": false,
"type": "module",
"files": ["dist"],
"style": "./dist/index.css",
"exports": {
".": {
"default": "./dist/index.css"
}
},
"sideEffects": ["*.css"],
"scripts": {
"build": "rimraf dist && ncp src dist && node ./scripts/build.mjs"
},
"dependencies": {},
"devDependencies": {
"ncp": "^2.0.0",
"rimraf": "^6.1.2"
}
}With optional exports (e.g. colors)
Add additional export entries pointing to dist paths. Each opt-in module gets its own export:
json
{
"exports": {
".": {
"default": "./dist/index.css"
},
"./scales/main/blue": {
"default": "./dist/scales/main/scales-main-blue.css"
},
"./palettes/flatui": {
"default": "./dist/palettes/flatui/palette-flatui.css"
}
}
}Consumers import opt-ins individually:
css
@import "@vibe-labs/design-colors";
@import "@vibe-labs/design-colors/scales/main/blue";Build Pipeline
Every package uses the same build command in package.json:
json
"build": "rimraf dist && ncp src dist && node ./scripts/build.mjs"- Clean —
rimraf distremoves stale output - Copy —
ncp src distcopies all source CSS (tokens) into dist as-is - Generate —
build.mjscreates{name}.g.css(generated utilities) and overwritesindex.csswith correct barrel imports
Source CSS: Token Definitions
Tokens live in src/{name}.css (or multiple files in subdirectories). All tokens are defined inside @layer vibe.tokens on :root:
css
@layer vibe.tokens {
:root {
/* Semantic tokens referencing lower-level tokens */
--surface-background: var(--color-neutral-950);
--surface-base: var(--color-neutral-900);
/* Primitive tokens with direct values */
--blur-sm: 4px;
--blur-md: 8px;
--opacity-50: 0.5;
}
}Rules
- Always
@layer vibe.tokens - Always
:rootscope - Semantic tokens reference other packages' primitives via
var(--...) - Primitive tokens use direct values (px, rem, hex, rgba, etc.)
- Document cross-package dependencies in the readme
Build Script: Generating Utilities
scripts/build.mjs generates utility classes into @layer vibe.utilities. Pattern:
js
import fs from "fs";
import path from "path";
const distDir = path.resolve("dist");
// Wrap all generated CSS in the utilities layer
function l(txt) {
return `@layer vibe.utilities {\n${txt}}\n`;
}
/* ── Category heading ── */
function generateSomethingUtilities() {
let output = "";
// Map tokens to utility classes
output += `.util-name { property: var(--token-name); }\n`;
return output;
}
/* ── Write all ── */
const all = [generateSomethingUtilities(), generateOtherUtilities()].join("");
fs.writeFileSync(path.join(distDir, "{name}.g.css"), l(all));
fs.writeFileSync(path.join(distDir, "index.css"), `@import "./{name}.css";\n@import "./{name}.g.css";\n`);Generation patterns
Token-to-class mapping (most common):
js
const steps = [0, 5, 10, 20, 25, 50, 75, 80, 90, 95, 100];
for (const n of steps) {
output += `.opacity-${n} { opacity: var(--opacity-${n}); }\n`;
}Named utilities (small finite set):
js
output += `.backdrop-blur-none { backdrop-filter: none; }\n`;
output += `.backdrop-blur-sm { backdrop-filter: blur(var(--blur-sm)); }\n`;Semantic aliases (map short class to semantic token):
js
output += `.bg-background { background-color: var(--surface-background); }\n`;
output += `.bg-base { background-color: var(--surface-base); }\n`;Enum-style utilities (CSS values iterated):
js
for (const m of ["normal", "multiply", "screen", "overlay", "darken", "lighten"]) {
output += `.bg-blend-${m} { background-blend-mode: ${m}; }\n`;
}Barrel: index.css
Source (src/index.css)
Imports the hand-authored token files only:
css
@import "./{name}.css";Or for packages with multiple source files:
css
@import "./scales/neutral/scales-neutral.css";
@import "./scales/scales-status.css";Dist (dist/index.css) — overwritten by build
The build script overwrites this to include both tokens and generated utilities:
css
@import "./{name}.css";
@import "./{name}.g.css";CSS Layer Order
All packages participate in the global layer stack:
vibe.reset → vibe.tokens → vibe.utilities → vibe.components → vibe.theme → vibe.accessibility- Token files →
@layer vibe.tokens - Generated utilities →
@layer vibe.utilities - Unlayered CSS (tenant overrides) wins over all layers automatically
Readme Structure
Every package readme follows this template:
markdown
# @vibe-labs/design-{name}
One-line description of what this package provides.
## Usage
\```css
@import "@vibe-labs/design-{name}";
\```
## Contents
### Tokens (`{name}.css`)
Document every token with its default value.
### Generated Utilities (`{name}.g.css`)
List every generated utility class.
## Dist Structure
| File | Description |
| -------------- | --------------------------------- |
| `index.css` | Barrel — imports both files below |
| `{name}.css` | Token definitions |
| `{name}.g.css` | Generated utility classes |
## Dependencies
List which tokens from other packages are required
(e.g. `--color-neutral-*` from `@vibe-labs/design-colors`).
## Build
\```bash
npm run build
\```Cross-Package Dependencies
Packages reference each other's tokens via CSS custom properties. These are runtime dependencies (the tokens must be loaded in the cascade), not npm dependencies.
Document them in the readme but don't add them to package.json dependencies — the umbrella @vibe-labs/design package handles import order.
Common dependency chains:
- surfaces requires
--color-neutral-*(colors) and--text-*(typography) - elevation requires
--color-accent(theme) - forms requires tokens from borders, typography, and colors
Variant System
Component CSS (at the vibe.components layer) uses data-attribute selectors by default:
html
<button class="btn" data-variant="accent" data-size="lg">Click me</button>Flat class mode also available: .btn-accent · .btn-lg
This is relevant context but not something design-level packages implement — it's handled at the component layer.
Quick Checklist: New Package
- Create
vibe-design-{name}/directory - Add
package.json(copy standard template, update name) - Create
src/{name}.csswith tokens in@layer vibe.tokens - Create
src/index.cssbarrel importing your token file(s) - Create
scripts/build.mjsgenerating utilities into@layer vibe.utilities - Write
readme.mddocumenting all tokens and utilities - Run
npm run buildand verify dist output - Add the package to the umbrella
@vibe-labs/designimports