@launch77-shared/lib-design-system 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 ADDED
@@ -0,0 +1,120 @@
1
+ # @launch77-shared/lib-design-system
2
+
3
+ Token-driven design system providing a theming foundation for Launch77 applications. Built for seamless integration with shadcn/ui component libraries.
4
+
5
+ ## Installation
6
+
7
+ **Recommended:** Install via Launch77 plugin (handles all configuration automatically):
8
+
9
+ ```bash
10
+ launch77 plugin:install design-system
11
+ ```
12
+
13
+ **Manual installation** (not recommended):
14
+
15
+ ```bash
16
+ npm install @launch77-shared/lib-design-system
17
+ ```
18
+
19
+ > **Note:** Manual installation requires configuring Tailwind preset and CSS imports yourself. The plugin automates this setup.
20
+
21
+ ## Features
22
+
23
+ - 🎨 **Semantic Token System** - CSS custom properties in HSL format with alpha channel support
24
+ - 🌗 **Dark Mode** - Complete dark mode support via `.dark` class
25
+ - ⚡ **Tailwind Preset** - Ready-to-use Tailwind configuration with theme extensions
26
+ - ♿ **Accessibility First** - Auto-injected base styles with focus rings and reduced motion support
27
+ - 🎬 **Animations** - 8 built-in keyframe animations (accordion, fade, slide)
28
+ - 🎯 **Brand Customization** - Simple CSS cascade for per-app theming
29
+ - 🔧 **shadcn Compatible** - Follows shadcn/ui token architecture
30
+
31
+ ## What's Included
32
+
33
+ ### Semantic Color Tokens
34
+
35
+ ```css
36
+ /* Available in light and dark modes */
37
+ --color-background / --color-foreground
38
+ --color-primary / --color-primary-foreground
39
+ --color-secondary / --color-secondary-foreground
40
+ --color-accent / --color-accent-foreground
41
+ --color-muted / --color-muted-foreground
42
+ --color-destructive / --color-destructive-foreground
43
+ --color-card / --color-card-foreground
44
+ --color-border
45
+ --color-input
46
+ --color-ring
47
+ ```
48
+
49
+ ### Tailwind Utilities
50
+
51
+ All semantic colors available as Tailwind utilities with alpha channel support:
52
+
53
+ ```tsx
54
+ <div className="bg-primary/20 text-foreground border-input" />
55
+ ```
56
+
57
+ ### Animations
58
+
59
+ 8 pre-built animations: fade-in/out, slide-in (4 directions), accordion-up/down.
60
+
61
+ All animations automatically respect `prefers-reduced-motion` preferences.
62
+
63
+ ## Documentation
64
+
65
+ - **[Complete Documentation](./docs/README.md)** - Comprehensive guide with:
66
+ - Design system concepts and philosophy
67
+ - How to build components with tokens
68
+ - Token system reference
69
+ - Architecture overview
70
+ - Dark mode implementation details
71
+ - TypeScript API reference
72
+
73
+ - **Plugin README** - For installation and usage instructions:
74
+ - Quick start guide
75
+ - Usage examples (buttons, cards, forms)
76
+ - Brand customization
77
+ - Troubleshooting
78
+
79
+ ## Quick Reference
80
+
81
+ ### Using with Tailwind (if manually installed)
82
+
83
+ ```js
84
+ // tailwind.config.js
85
+ const preset = require('@launch77-shared/lib-design-system/preset')
86
+
87
+ module.exports = {
88
+ presets: [preset],
89
+ content: ['./src/**/*.{ts,tsx}'],
90
+ }
91
+ ```
92
+
93
+ ### Importing Tokens (if manually installed)
94
+
95
+ ```css
96
+ /* globals.css */
97
+ @tailwind base;
98
+ @tailwind components;
99
+ @tailwind utilities;
100
+
101
+ @import '@launch77-shared/lib-design-system/tokens.css';
102
+ ```
103
+
104
+ ### HSL Color Format
105
+
106
+ Colors use HSL format for flexibility:
107
+
108
+ ```css
109
+ --color-primary: 222 47% 11%; /* Hue Saturation% Lightness% */
110
+ ```
111
+
112
+ Benefits: Alpha channel support (`bg-primary/20`), easy to adjust, color picker compatible.
113
+
114
+ ## TypeScript Support
115
+
116
+ Full TypeScript definitions included for Tailwind configuration.
117
+
118
+ ## License
119
+
120
+ UNLICENSED - Internal Launch77 use only.
package/docs/README.md ADDED
@@ -0,0 +1,883 @@
1
+ # Launch77 Design System - Complete Guide
2
+
3
+ This design system provides a token-driven theming foundation for Launch77 applications and shared component libraries. It enables consistent, accessible, and brand-customizable user interfaces through semantic design tokens and Tailwind CSS integration.
4
+
5
+ > **Installation:** Use the Launch77 plugin for automatic setup: `launch77 plugin:install design-system`
6
+
7
+ ## Table of Contents
8
+
9
+ ### Conceptual Guide
10
+
11
+ 1. [What is a Design System?](#what-is-a-design-system)
12
+ 2. [How to Build Components](#how-to-build-components)
13
+ 3. [Token Philosophy](#token-philosophy)
14
+ 4. [Common Patterns](#common-patterns)
15
+ 5. [When to Use Tokens](#when-to-use-tokens-vs-hardcoded-values)
16
+
17
+ ### Technical Reference
18
+
19
+ 6. [Architecture Overview](#architecture-overview)
20
+ 7. [Token System](#token-system)
21
+ 8. [Tailwind Preset](#tailwind-preset)
22
+ 9. [Dark Mode](#dark-mode)
23
+ 10. [Animations](#animations)
24
+ 11. [Accessibility Features](#accessibility-features)
25
+
26
+ ---
27
+
28
+ ## Conceptual Guide
29
+
30
+ ### What is a Design System?
31
+
32
+ A design system is a **theming foundation** that separates visual decisions (colors, spacing) from component structure (buttons, cards). Think of it as a contract between designers and developers:
33
+
34
+ - **Designers** define what "primary" means (your brand color)
35
+ - **Developers** build components that reference "primary" (not hardcoded blue)
36
+ - **Result**: Change the brand color once, and all components update automatically
37
+
38
+ #### Why Use Semantic Tokens?
39
+
40
+ Instead of naming colors by their appearance (`blue-500`, `red-600`), we name them by their **meaning**:
41
+
42
+ ```tsx
43
+ // ❌ BAD: Hardcoded appearance
44
+ <button className="bg-blue-500 text-white">Submit</button>
45
+
46
+ // ✅ GOOD: Semantic meaning
47
+ <button className="bg-primary text-primary-foreground">Submit</button>
48
+ ```
49
+
50
+ **Benefits:**
51
+
52
+ - **Rebrand easily**: Change `--color-primary` once, all buttons update
53
+ - **Dark mode**: Tokens adapt automatically (no component changes needed)
54
+ - **Consistency**: All "primary" buttons look identical across the app
55
+ - **Maintainability**: Designers control colors, not component code
56
+
57
+ #### Real-World Example
58
+
59
+ Your company rebrands from blue to green:
60
+
61
+ ```css
62
+ /* Old brand */
63
+ --color-primary: 220 60% 50%; /* Blue */
64
+
65
+ /* New brand */
66
+ --color-primary: 150 60% 50%; /* Green */
67
+ ```
68
+
69
+ All buttons, links, and primary elements update instantly - **no component code changes required**.
70
+
71
+ ---
72
+
73
+ ### How to Build Components
74
+
75
+ When building components with this design system, follow these rules:
76
+
77
+ #### Rule #1: Always Use Semantic Tokens
78
+
79
+ **Every color in your component should use a semantic token:**
80
+
81
+ ```tsx
82
+ // ✅ CORRECT
83
+ function Button() {
84
+ return <button className="bg-primary text-primary-foreground hover:bg-primary/90">Click me</button>
85
+ }
86
+
87
+ // ❌ WRONG
88
+ function Button() {
89
+ return <button className="bg-blue-500 text-white hover:bg-blue-600">Click me</button>
90
+ }
91
+ ```
92
+
93
+ #### Rule #2: Never Hardcode Colors
94
+
95
+ **Don't use Tailwind's default color palette:**
96
+
97
+ - ❌ Never: `bg-blue-500`, `text-gray-600`, `border-red-400`
98
+ - ✅ Always: `bg-primary`, `text-muted-foreground`, `border-destructive`
99
+
100
+ #### Rule #3: Use Semantic Utilities for Meaning
101
+
102
+ **Match the token to the component's purpose:**
103
+
104
+ ```tsx
105
+ // Primary action → bg-primary
106
+ <button className="bg-primary text-primary-foreground">Save</button>
107
+
108
+ // Destructive action → bg-destructive
109
+ <button className="bg-destructive text-destructive-foreground">Delete</button>
110
+
111
+ // Secondary action → bg-secondary
112
+ <button className="bg-secondary text-secondary-foreground">Cancel</button>
113
+
114
+ // Error message → text-destructive
115
+ <p className="text-destructive">Error: Invalid input</p>
116
+
117
+ // Subtle hint → text-muted-foreground
118
+ <p className="text-muted-foreground">Optional field</p>
119
+ ```
120
+
121
+ #### Simple Decision Tree
122
+
123
+ When building a component, ask yourself:
124
+
125
+ 1. **What is this component's purpose?**
126
+ - Primary action? → Use `bg-primary`
127
+ - Secondary action? → Use `bg-secondary`
128
+ - Destructive action? → Use `bg-destructive`
129
+ - Neutral container? → Use `bg-card`
130
+ - Emphasis/highlight? → Use `bg-accent`
131
+
132
+ 2. **What is this text's role?**
133
+ - Main content? → Use `text-foreground`
134
+ - Subtle/helper text? → Use `text-muted-foreground`
135
+ - On colored background? → Use `text-primary-foreground` (matches the background)
136
+
137
+ 3. **What are these borders/inputs?**
138
+ - Standard border? → Use `border-border`
139
+ - Form input? → Use `border-input`
140
+ - Focus ring? → Use `ring-ring`
141
+
142
+ ---
143
+
144
+ ### Token Philosophy
145
+
146
+ #### Tokens = Design Decisions
147
+
148
+ Design tokens represent **design decisions**, not specific colors:
149
+
150
+ - `--color-primary` = "What color represents our brand?"
151
+ - `--color-destructive` = "What color means danger/error?"
152
+ - `--color-muted` = "What color is subtle but visible?"
153
+
154
+ These decisions can change (rebrand, dark mode, A/B tests), but the component code stays the same.
155
+
156
+ #### Components = Structure
157
+
158
+ Components define **structure and behavior**, not appearance:
159
+
160
+ ```tsx
161
+ // This component is "themeable" - it adapts to any theme
162
+ function Card({ children }) {
163
+ return <div className="bg-card text-card-foreground border border-border rounded-lg p-6">{children}</div>
164
+ }
165
+ ```
166
+
167
+ The card works with:
168
+
169
+ - Any brand color (tokens define primary/accent)
170
+ - Light or dark mode (tokens adapt automatically)
171
+ - Any app's brand identity (each app overrides tokens)
172
+
173
+ #### The Power of Separation
174
+
175
+ By separating tokens (design) from components (structure), you get:
176
+
177
+ ```tsx
178
+ // Component code (never changes)
179
+ <button className="bg-primary text-primary-foreground">Save</button>
180
+
181
+ // Theme 1: Blue brand
182
+ --color-primary: 220 60% 50%;
183
+
184
+ // Theme 2: Green brand
185
+ --color-primary: 150 60% 50%;
186
+
187
+ // Theme 3: Dark mode blue
188
+ --color-primary: 220 70% 60%;
189
+ ```
190
+
191
+ Same component, different visual appearance - **zero code changes**.
192
+
193
+ ---
194
+
195
+ ### Common Patterns
196
+
197
+ Here's a quick reference for which tokens to use with common component types:
198
+
199
+ | Component Type | Recommended Tokens | Example |
200
+ | ---------------------- | -------------------------------------------------- | -------------------- |
201
+ | **Primary Button** | `bg-primary` `text-primary-foreground` | Save, Submit, Create |
202
+ | **Secondary Button** | `bg-secondary` `text-secondary-foreground` | Cancel, Back |
203
+ | **Destructive Button** | `bg-destructive` `text-destructive-foreground` | Delete, Remove |
204
+ | **Outline Button** | `border-input` `text-foreground` `hover:bg-accent` | Optional actions |
205
+ | **Ghost Button** | `hover:bg-accent` `text-foreground` | Icon buttons |
206
+ | **Card Container** | `bg-card` `text-card-foreground` `border-border` | Content cards |
207
+ | **Main Content** | `bg-background` `text-foreground` | Page layout |
208
+ | **Muted Container** | `bg-muted` `text-muted-foreground` | Subtle backgrounds |
209
+ | **Error Message** | `text-destructive` `border-destructive` | Validation errors |
210
+ | **Success Message** | `text-primary` `border-primary` | Success states |
211
+ | **Helper Text** | `text-muted-foreground` | Labels, hints |
212
+ | **Form Input** | `border-input` `bg-background` `ring-ring` | Text inputs |
213
+ | **Hover States** | `hover:bg-primary/90` (use alpha) | Interactive elements |
214
+
215
+ #### Real Component Examples
216
+
217
+ **Button Component with Variants:**
218
+
219
+ ```tsx
220
+ import { cva } from 'class-variance-authority'
221
+
222
+ const button = cva(
223
+ // Base styles (always applied)
224
+ 'inline-flex items-center justify-center rounded-lg px-4 py-2 font-medium transition-colors',
225
+ {
226
+ variants: {
227
+ variant: {
228
+ primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
229
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
230
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
231
+ outline: 'border border-input bg-background hover:bg-accent text-foreground',
232
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
233
+ },
234
+ },
235
+ }
236
+ )
237
+ ```
238
+
239
+ **Card Component:**
240
+
241
+ ```tsx
242
+ function Card({ children, className = '' }) {
243
+ return <div className={`bg-card text-card-foreground border border-border rounded-lg p-6 ${className}`}>{children}</div>
244
+ }
245
+ ```
246
+
247
+ **Form Input:**
248
+
249
+ ```tsx
250
+ function Input({ className = '', ...props }) {
251
+ return <input className={`w-full px-3 py-2 bg-background text-foreground border border-input rounded-md focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${className}`} {...props} />
252
+ }
253
+ ```
254
+
255
+ **Alert Component:**
256
+
257
+ ```tsx
258
+ function Alert({ variant = 'default', children }) {
259
+ const styles = {
260
+ default: 'bg-card text-card-foreground border-border',
261
+ destructive: 'bg-destructive/10 text-destructive border-destructive',
262
+ success: 'bg-primary/10 text-primary border-primary',
263
+ }
264
+
265
+ return <div className={`border rounded-lg p-4 ${styles[variant]}`}>{children}</div>
266
+ }
267
+ ```
268
+
269
+ ---
270
+
271
+ ### When to Use Tokens vs Hardcoded Values
272
+
273
+ #### ✅ Use Tokens When...
274
+
275
+ **Colors should adapt to theme or brand:**
276
+
277
+ ```tsx
278
+ // ✅ Button color should match brand
279
+ <button className="bg-primary text-primary-foreground">Submit</button>
280
+
281
+ // ✅ Text should adapt to light/dark mode
282
+ <p className="text-foreground">Main content</p>
283
+
284
+ // ✅ Card backgrounds should match theme
285
+ <div className="bg-card border-border">...</div>
286
+
287
+ // ✅ Interactive states should use theme colors
288
+ <button className="hover:bg-primary/90">Hover me</button>
289
+ ```
290
+
291
+ **Components that will be reused:**
292
+
293
+ - Buttons, cards, inputs, navigation, etc.
294
+ - Any UI element users interact with
295
+ - Layout containers
296
+ - Text content
297
+
298
+ #### ❌ Don't Use Tokens For...
299
+
300
+ **Decorative elements that shouldn't change:**
301
+
302
+ ```tsx
303
+ // ❌ Company logo color is part of brand identity
304
+ <svg className="fill-[#FF5816]">Logo</svg>
305
+
306
+ // ❌ Specific illustrations have fixed colors
307
+ <img src="/hero-illustration.svg" alt="Hero" />
308
+
309
+ // ❌ Chart colors need to be distinct (not theme-dependent)
310
+ <Bar data={data} colors={['#ff0000', '#00ff00', '#0000ff']} />
311
+
312
+ // ❌ Code syntax highlighting (needs specific colors)
313
+ <code className="language-js">...</code>
314
+ ```
315
+
316
+ **One-off decorative flourishes:**
317
+
318
+ - Gradients in marketing headers
319
+ - Specific icons with fixed colors
320
+ - Photography and illustrations
321
+ - Data visualizations that need precise colors
322
+ - Third-party embedded content
323
+
324
+ #### Simple Rule of Thumb
325
+
326
+ **Ask yourself: "If the user switches to dark mode or we rebrand, should this change color?"**
327
+
328
+ - **Yes** → Use a semantic token
329
+ - **No** → Use a hardcoded value
330
+
331
+ #### Examples in Context
332
+
333
+ ```tsx
334
+ function ProductCard({ product }) {
335
+ return (
336
+ {/* ✅ Card uses theme tokens (adapts to theme) */}
337
+ <div className="bg-card text-card-foreground border border-border rounded-lg p-4">
338
+
339
+ {/* ❌ Product image doesn't change with theme */}
340
+ <img src={product.image} alt={product.name} />
341
+
342
+ {/* ✅ Text uses theme tokens (adapts to light/dark) */}
343
+ <h3 className="text-foreground font-bold">{product.name}</h3>
344
+ <p className="text-muted-foreground">{product.description}</p>
345
+
346
+ {/* ❌ Product category badge has fixed brand color */}
347
+ <span className="bg-[#FF5816] text-white px-2 py-1 rounded">
348
+ {product.category}
349
+ </span>
350
+
351
+ {/* ✅ Action button uses theme tokens */}
352
+ <button className="bg-primary text-primary-foreground hover:bg-primary/90">
353
+ Add to Cart
354
+ </button>
355
+ </div>
356
+ )
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Technical Reference
363
+
364
+ ### Architecture Overview
365
+
366
+ The design system consists of three layers:
367
+
368
+ #### 1. CSS Tokens (`tokens.css`)
369
+
370
+ Pure semantic CSS custom properties with no framework dependencies. These define the visual language of your application:
371
+
372
+ ```css
373
+ :root {
374
+ --color-background: 0 0% 100%;
375
+ --color-foreground: 222 47% 11%;
376
+ --color-primary: 222 47% 11%;
377
+ /* ... more tokens */
378
+ }
379
+
380
+ .dark {
381
+ --color-background: 222 47% 11%;
382
+ --color-foreground: 210 40% 98%;
383
+ --color-primary: 217 91% 60%;
384
+ /* ... dark mode overrides */
385
+ }
386
+ ```
387
+
388
+ **Benefits:**
389
+
390
+ - Framework-agnostic (works with any CSS)
391
+ - Cascades naturally (easy to override)
392
+ - No build step required
393
+
394
+ #### 2. Tailwind Preset (`preset.js`)
395
+
396
+ Maps Tailwind utilities to the CSS tokens and includes a plugin that auto-injects base styles:
397
+
398
+ ```js
399
+ module.exports = {
400
+ theme: {
401
+ extend: {
402
+ colors: {
403
+ background: 'hsl(var(--color-background) / <alpha-value>)',
404
+ foreground: 'hsl(var(--color-foreground) / <alpha-value>)',
405
+ primary: {
406
+ DEFAULT: 'hsl(var(--color-primary) / <alpha-value>)',
407
+ foreground: 'hsl(var(--color-primary-foreground) / <alpha-value>)',
408
+ },
409
+ // ... more mappings
410
+ },
411
+ },
412
+ },
413
+ plugins: [
414
+ /* Auto-inject base styles */
415
+ ],
416
+ }
417
+ ```
418
+
419
+ **Benefits:**
420
+
421
+ - Tailwind utilities automatically use tokens
422
+ - Alpha channel support (`bg-primary/20`)
423
+ - No manual configuration needed
424
+
425
+ #### 3. App-Level Customization
426
+
427
+ Each app can override tokens via its own `brand.css` file without touching shared components:
428
+
429
+ ```css
430
+ /* app/brand.css */
431
+ :root {
432
+ --color-primary: 158 64% 52%; /* Override with brand color */
433
+ --color-accent: 156 72% 72%;
434
+ }
435
+
436
+ .dark {
437
+ --color-primary: 158 64% 52%; /* Dark mode override */
438
+ }
439
+ ```
440
+
441
+ **Result:** Components stay neutral and reusable, while each app maintains its unique brand identity.
442
+
443
+ ---
444
+
445
+ ### Token System
446
+
447
+ #### Understanding HSL Format
448
+
449
+ All color tokens use HSL (Hue, Saturation, Lightness) format without the `hsl()` wrapper:
450
+
451
+ ```css
452
+ --color-primary: 222 47% 11%;
453
+ /* ^^^ ^^ ^^
454
+ H S L */
455
+ ```
456
+
457
+ **Why no `hsl()` wrapper?**
458
+
459
+ This format enables Tailwind's alpha channel syntax:
460
+
461
+ ```tsx
462
+ {/* Tailwind adds hsl() and alpha automatically */}
463
+ <div className="bg-primary/20"> {/* → hsl(222 47% 11% / 0.2) */}
464
+ ```
465
+
466
+ **HSL Benefits:**
467
+
468
+ - **Alpha Channel**: Use `/` for opacity (e.g., `bg-primary/20` = 20% opacity)
469
+ - **Human-Readable**: Easy to understand and adjust
470
+ - **Design Tool Compatible**: Most color pickers support HSL
471
+ - **shadcn Standard**: Matches shadcn/ui architecture
472
+
473
+ **Converting from other formats:**
474
+
475
+ ```
476
+ Hex #3730A3 → HSL 243 58% 41%
477
+ RGB 55 48 163 → HSL 243 58% 41%
478
+
479
+ Use online tools: hslpicker.com or browser DevTools color picker
480
+ ```
481
+
482
+ #### Complete Token Reference
483
+
484
+ ##### Base Colors
485
+
486
+ ```css
487
+ --color-background /* Main app background (usually white/black) */
488
+ --color-foreground /* Main text color (usually black/white) */
489
+ ```
490
+
491
+ **When to use:**
492
+
493
+ - `bg-background` - Page layout, main containers
494
+ - `text-foreground` - Primary text content
495
+
496
+ ##### Card
497
+
498
+ ```css
499
+ --color-card /* Card background (slightly elevated) */
500
+ --color-card-foreground /* Card text color */
501
+ ```
502
+
503
+ **When to use:**
504
+
505
+ - `bg-card` - Content cards, modals, dropdown menus
506
+ - `text-card-foreground` - Text inside cards
507
+
508
+ ##### Primary (Brand Color)
509
+
510
+ ```css
511
+ --color-primary /* Main brand color */
512
+ --color-primary-foreground /* Text on primary background */
513
+ ```
514
+
515
+ **When to use:**
516
+
517
+ - `bg-primary` - Primary action buttons, active states, brand elements
518
+ - `text-primary` - Links, highlighted text
519
+ - `text-primary-foreground` - Text on primary backgrounds
520
+
521
+ ##### Secondary
522
+
523
+ ```css
524
+ --color-secondary /* Secondary actions/elements */
525
+ --color-secondary-foreground /* Text on secondary background */
526
+ ```
527
+
528
+ **When to use:**
529
+
530
+ - `bg-secondary` - Secondary buttons, less important actions
531
+ - `text-secondary-foreground` - Text on secondary backgrounds
532
+
533
+ ##### Accent
534
+
535
+ ```css
536
+ --color-accent /* Accent color for highlights */
537
+ --color-accent-foreground /* Text on accent background */
538
+ ```
539
+
540
+ **When to use:**
541
+
542
+ - `bg-accent` - Hover states, highlights, badges
543
+ - `hover:bg-accent` - Interactive element hover states
544
+
545
+ ##### Muted
546
+
547
+ ```css
548
+ --color-muted /* Subtle backgrounds */
549
+ --color-muted-foreground /* Subtle text color */
550
+ ```
551
+
552
+ **When to use:**
553
+
554
+ - `bg-muted` - Disabled states, subtle containers
555
+ - `text-muted-foreground` - Helper text, labels, secondary info
556
+
557
+ ##### Destructive
558
+
559
+ ```css
560
+ --color-destructive /* Destructive actions (delete, error) */
561
+ --color-destructive-foreground /* Text on destructive background */
562
+ ```
563
+
564
+ **When to use:**
565
+
566
+ - `bg-destructive` - Delete buttons, error alerts
567
+ - `text-destructive` - Error messages, validation failures
568
+ - `border-destructive` - Error input borders
569
+
570
+ ##### Form Elements
571
+
572
+ ```css
573
+ --color-border /* Standard border color */
574
+ --color-input /* Input border color (slightly darker) */
575
+ --color-ring /* Focus ring color (usually primary) */
576
+ ```
577
+
578
+ **When to use:**
579
+
580
+ - `border-border` - Card borders, dividers
581
+ - `border-input` - Form input borders
582
+ - `ring-ring` - Focus rings on interactive elements
583
+
584
+ ##### Border Radius
585
+
586
+ ```css
587
+ --radius: 1rem /* Base border radius (16px) */;
588
+ ```
589
+
590
+ **Tailwind utilities automatically use this:**
591
+
592
+ - `rounded-lg` → `var(--radius)` (16px)
593
+ - `rounded-md` → `calc(var(--radius) - 2px)` (14px)
594
+ - `rounded-sm` → `calc(var(--radius) - 4px)` (12px)
595
+
596
+ ---
597
+
598
+ ### Tailwind Preset
599
+
600
+ #### What the Preset Provides
601
+
602
+ The `preset.js` file exports a Tailwind configuration object that:
603
+
604
+ 1. **Maps all tokens to Tailwind utilities**
605
+ 2. **Enables alpha channel support** (`/` syntax)
606
+ 3. **Auto-injects base styles** via plugin
607
+ 4. **Configures border radius** utilities
608
+ 5. **Adds animation keyframes**
609
+
610
+ #### Configuration API
611
+
612
+ ```js
613
+ // tailwind.config.js
614
+ const preset = require('@launch77-shared/lib-design-system/preset')
615
+
616
+ /** @type {import('tailwindcss').Config} */
617
+ module.exports = {
618
+ presets: [preset],
619
+ content: ['./src/**/*.{ts,tsx,js,jsx}', './app/**/*.{ts,tsx,js,jsx}'],
620
+ }
621
+ ```
622
+
623
+ #### Alpha Channel Support
624
+
625
+ All color tokens support Tailwind's alpha channel syntax:
626
+
627
+ ```tsx
628
+ <div className="bg-primary/10">10% opacity</div>
629
+ <div className="bg-primary/20">20% opacity</div>
630
+ <div className="bg-primary/50">50% opacity</div>
631
+ <div className="bg-primary/90">90% opacity</div>
632
+
633
+ <button className="bg-primary/90 hover:bg-primary">
634
+ Hover to full opacity
635
+ </button>
636
+ ```
637
+
638
+ **How it works:**
639
+
640
+ The preset maps tokens like this:
641
+
642
+ ```js
643
+ colors: {
644
+ primary: 'hsl(var(--color-primary) / <alpha-value>)',
645
+ // ^^^^^^^^^^^^^^
646
+ // Tailwind replaces this
647
+ }
648
+ ```
649
+
650
+ When you write `bg-primary/20`, Tailwind generates:
651
+
652
+ ```css
653
+ .bg-primary\/20 {
654
+ background-color: hsl(var(--color-primary) / 0.2);
655
+ }
656
+ ```
657
+
658
+ #### Auto-Injected Base Styles
659
+
660
+ The preset's plugin automatically adds these base styles (no manual copying needed):
661
+
662
+ ```css
663
+ * {
664
+ border-color: hsl(var(--color-border));
665
+ }
666
+
667
+ html {
668
+ scroll-behavior: smooth;
669
+ }
670
+
671
+ body {
672
+ background-color: hsl(var(--color-background));
673
+ color: hsl(var(--color-foreground));
674
+ }
675
+
676
+ /* Plus: selection styles, focus rings, tap targets, reduced motion */
677
+ ```
678
+
679
+ ---
680
+
681
+ ### Dark Mode
682
+
683
+ #### Implementation Strategy
684
+
685
+ Dark mode uses **class-based toggling** on the `<html>` element:
686
+
687
+ ```html
688
+ <!-- Light mode (default) -->
689
+ <html>
690
+ ...
691
+ </html>
692
+
693
+ <!-- Dark mode (add .dark class) -->
694
+ <html class="dark">
695
+ ...
696
+ </html>
697
+ ```
698
+
699
+ #### How It Works
700
+
701
+ Tokens are defined twice - once in `:root` (light mode), once in `.dark` (dark mode):
702
+
703
+ ```css
704
+ /* tokens.css */
705
+ :root {
706
+ --color-background: 0 0% 100%; /* White */
707
+ --color-foreground: 222 47% 11%; /* Dark gray */
708
+ --color-primary: 222 47% 11%; /* Dark blue */
709
+ }
710
+
711
+ .dark {
712
+ --color-background: 222 47% 11%; /* Dark gray */
713
+ --color-foreground: 210 40% 98%; /* Off-white */
714
+ --color-primary: 217 91% 60%; /* Light blue */
715
+ }
716
+ ```
717
+
718
+ When `.dark` is added to `<html>`, the CSS cascade applies dark mode values. Components don't change - they still use `bg-background`, `text-foreground`, etc.
719
+
720
+ #### Technical Details
721
+
722
+ **Why class-based instead of `prefers-color-scheme`?**
723
+
724
+ - User can override system preference
725
+ - Easy to persist in localStorage
726
+ - Simple to toggle programmatically
727
+ - Works in all browsers
728
+
729
+ **Why on `<html>` instead of `<body>`?**
730
+
731
+ - Tailwind's dark mode selector targets `:root.dark`
732
+ - Ensures all elements inherit correct mode
733
+ - Fixes edge cases with portals and modals
734
+
735
+ ---
736
+
737
+ ### Animations
738
+
739
+ #### Available Keyframes
740
+
741
+ The preset provides 8 animation keyframes:
742
+
743
+ ```css
744
+ @keyframes fadeIn
745
+ @keyframes fadeOut
746
+ @keyframes slideInFromTop
747
+ @keyframes slideInFromBottom
748
+ @keyframes slideInFromLeft
749
+ @keyframes slideInFromRight
750
+ @keyframes accordionDown
751
+ @keyframes accordionUp;
752
+ ```
753
+
754
+ #### Tailwind Utilities
755
+
756
+ ```tsx
757
+ {/* Fade animations */}
758
+ <div className="animate-fade-in">Fades in</div>
759
+ <div className="animate-fade-out">Fades out</div>
760
+
761
+ {/* Slide animations */}
762
+ <div className="animate-slide-in-from-top">Slides from top</div>
763
+ <div className="animate-slide-in-from-bottom">Slides from bottom</div>
764
+ <div className="animate-slide-in-from-left">Slides from left</div>
765
+ <div className="animate-slide-in-from-right">Slides from right</div>
766
+
767
+ {/* Accordion animations (for Radix UI) */}
768
+ <div className="animate-accordion-down">Expands</div>
769
+ <div className="animate-accordion-up">Collapses</div>
770
+ ```
771
+
772
+ #### Accessibility
773
+
774
+ All animations automatically respect `prefers-reduced-motion`:
775
+
776
+ ```css
777
+ @media (prefers-reduced-motion: reduce) {
778
+ *,
779
+ *::before,
780
+ *::after {
781
+ animation-duration: 0.01ms !important;
782
+ animation-iteration-count: 1 !important;
783
+ }
784
+ }
785
+ ```
786
+
787
+ Users who have enabled reduced motion in their OS settings will see instant state changes instead of animations.
788
+
789
+ ---
790
+
791
+ ### Accessibility Features
792
+
793
+ The preset auto-injects several accessibility features:
794
+
795
+ #### Focus Rings
796
+
797
+ ```css
798
+ /* Automatically applied to all interactive elements */
799
+ *:focus-visible {
800
+ outline: none;
801
+ box-shadow: 0 0 0 2px hsl(var(--color-ring));
802
+ }
803
+ ```
804
+
805
+ Override per-component if needed:
806
+
807
+ ```tsx
808
+ <button className="focus-visible:ring-2 focus-visible:ring-offset-2">Custom focus ring</button>
809
+ ```
810
+
811
+ #### Minimum Tap Targets
812
+
813
+ ```css
814
+ /* Interactive elements are at least 44x44px */
815
+ button,
816
+ [role='button'],
817
+ input,
818
+ select,
819
+ textarea,
820
+ a {
821
+ min-height: 44px;
822
+ min-width: 44px;
823
+ }
824
+ ```
825
+
826
+ #### Reduced Motion
827
+
828
+ ```css
829
+ @media (prefers-reduced-motion: reduce) {
830
+ /* Disables animations for users who prefer reduced motion */
831
+ animation-duration: 0.01ms !important;
832
+ transition-duration: 0.01ms !important;
833
+ }
834
+ ```
835
+
836
+ #### Smooth Scrolling
837
+
838
+ ```css
839
+ html {
840
+ scroll-behavior: smooth;
841
+ }
842
+
843
+ @media (prefers-reduced-motion: reduce) {
844
+ html {
845
+ scroll-behavior: auto;
846
+ }
847
+ }
848
+ ```
849
+
850
+ ---
851
+
852
+ ## Additional Resources
853
+
854
+ ### Related Documentation
855
+
856
+ - **Plugin README**: Installation and usage guide (in plugin package)
857
+ - **Module README**: Per-app customization guide (installed by plugin)
858
+ - **Test Page**: Visual demonstration of all features (`/design-system-test` route)
859
+
860
+ ### External Resources
861
+
862
+ - [Tailwind CSS Documentation](https://tailwindcss.com)
863
+ - [shadcn/ui Components](https://ui.shadcn.com)
864
+ - [HSL Color Picker](https://hslpicker.com)
865
+ - [WebAIM: Color Contrast](https://webaim.org/articles/contrast/)
866
+
867
+ ### TypeScript Support
868
+
869
+ The library includes full TypeScript definitions:
870
+
871
+ ```ts
872
+ import type { Config } from 'tailwindcss'
873
+ import preset from '@launch77-shared/lib-design-system/preset'
874
+
875
+ const config: Config = {
876
+ presets: [preset],
877
+ // TypeScript knows about all preset options
878
+ }
879
+ ```
880
+
881
+ ---
882
+
883
+ **Need help?** Check the plugin README for troubleshooting, or ask in the Launch77 community.
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@launch77-shared/lib-design-system",
3
+ "version": "0.1.0",
4
+ "description": "Launch77 Design System - Token-driven theming foundation for shadcn-based component libraries",
5
+ "license": "UNLICENSED",
6
+ "main": "./src/preset.js",
7
+ "exports": {
8
+ ".": {
9
+ "default": "./src/preset.js"
10
+ },
11
+ "./tokens.css": "./src/tokens.css",
12
+ "./preset": "./src/preset.js"
13
+ },
14
+ "files": [
15
+ "src/preset.js",
16
+ "src/tokens.css",
17
+ "src/index.js",
18
+ "src/index.d.ts",
19
+ "docs/"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "echo 'No build required for design system package'",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "echo 'Tests not yet implemented'",
28
+ "release:connect": "launch77-release-connect",
29
+ "release:verify": "launch77-release-verify"
30
+ },
31
+ "peerDependencies": {
32
+ "tailwindcss": "^3.4.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "tailwindcss": "^3.4.17",
37
+ "typescript": "^5.3.0"
38
+ },
39
+ "launch77": {
40
+ "installedPlugins": {
41
+ "release": {
42
+ "package": "release",
43
+ "version": "1.0.0",
44
+ "installedAt": "2026-01-22T17:33:45.971Z",
45
+ "source": "local"
46
+ }
47
+ }
48
+ }
49
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'tailwindcss'
2
+
3
+ export const preset: Config
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ preset: require('./preset'),
3
+ }
package/src/preset.js ADDED
@@ -0,0 +1,139 @@
1
+ const plugin = require('tailwindcss/plugin')
2
+
3
+ // Base styles plugin - automatically injects base styles without requiring CSS imports
4
+ const baseStylesPlugin = plugin(function ({ addBase }) {
5
+ addBase({
6
+ '*': {
7
+ borderColor: 'hsl(var(--color-border))',
8
+ },
9
+ html: {
10
+ scrollBehavior: 'smooth',
11
+ },
12
+ body: {
13
+ backgroundColor: 'hsl(var(--color-background))',
14
+ color: 'hsl(var(--color-foreground))',
15
+ WebkitFontSmoothing: 'antialiased',
16
+ MozOsxFontSmoothing: 'grayscale',
17
+ },
18
+ '::selection': {
19
+ backgroundColor: 'hsl(var(--color-primary) / 0.2)',
20
+ color: 'hsl(var(--color-primary))',
21
+ },
22
+ ':focus-visible': {
23
+ outline: 'none',
24
+ boxShadow: '0 0 0 2px hsl(var(--color-background)), 0 0 0 4px hsl(var(--color-ring))',
25
+ },
26
+ 'button, a, input, select, textarea': {
27
+ minHeight: '44px',
28
+ minWidth: '44px',
29
+ },
30
+ '@media (prefers-reduced-motion: reduce)': {
31
+ '*, *::before, *::after': {
32
+ animationDuration: '0.01ms !important',
33
+ animationIterationCount: '1 !important',
34
+ transitionDuration: '0.01ms !important',
35
+ scrollBehavior: 'auto !important',
36
+ },
37
+ },
38
+ })
39
+ })
40
+
41
+ /** @type {import('tailwindcss').Config} */
42
+ module.exports = {
43
+ darkMode: ['class'],
44
+ theme: {
45
+ container: {
46
+ center: true,
47
+ padding: '2rem',
48
+ screens: {
49
+ '2xl': '1400px',
50
+ },
51
+ },
52
+ extend: {
53
+ colors: {
54
+ border: 'hsl(var(--color-border))',
55
+ input: 'hsl(var(--color-input))',
56
+ ring: 'hsl(var(--color-ring))',
57
+ background: 'hsl(var(--color-background))',
58
+ foreground: 'hsl(var(--color-foreground))',
59
+ primary: {
60
+ DEFAULT: 'hsl(var(--color-primary) / <alpha-value>)',
61
+ foreground: 'hsl(var(--color-primary-foreground))',
62
+ },
63
+ secondary: {
64
+ DEFAULT: 'hsl(var(--color-secondary) / <alpha-value>)',
65
+ foreground: 'hsl(var(--color-secondary-foreground))',
66
+ },
67
+ destructive: {
68
+ DEFAULT: 'hsl(var(--color-destructive) / <alpha-value>)',
69
+ foreground: 'hsl(var(--color-destructive-foreground))',
70
+ },
71
+ muted: {
72
+ DEFAULT: 'hsl(var(--color-muted) / <alpha-value>)',
73
+ foreground: 'hsl(var(--color-muted-foreground))',
74
+ },
75
+ accent: {
76
+ DEFAULT: 'hsl(var(--color-accent) / <alpha-value>)',
77
+ foreground: 'hsl(var(--color-accent-foreground))',
78
+ },
79
+ card: {
80
+ DEFAULT: 'hsl(var(--color-card))',
81
+ foreground: 'hsl(var(--color-card-foreground))',
82
+ },
83
+ },
84
+ borderRadius: {
85
+ lg: 'var(--radius)',
86
+ md: 'calc(var(--radius) - 2px)',
87
+ sm: 'calc(var(--radius) - 4px)',
88
+ },
89
+ keyframes: {
90
+ 'accordion-down': {
91
+ from: { height: '0' },
92
+ to: { height: 'var(--radix-accordion-content-height)' },
93
+ },
94
+ 'accordion-up': {
95
+ from: { height: 'var(--radix-accordion-content-height)' },
96
+ to: { height: '0' },
97
+ },
98
+ 'fade-in': {
99
+ from: { opacity: '0' },
100
+ to: { opacity: '1' },
101
+ },
102
+ 'fade-out': {
103
+ from: { opacity: '1' },
104
+ to: { opacity: '0' },
105
+ },
106
+ 'slide-in-from-top': {
107
+ from: { transform: 'translateY(-100%)' },
108
+ to: { transform: 'translateY(0)' },
109
+ },
110
+ 'slide-in-from-bottom': {
111
+ from: { transform: 'translateY(100%)' },
112
+ to: { transform: 'translateY(0)' },
113
+ },
114
+ 'slide-in-from-left': {
115
+ from: { transform: 'translateX(-100%)' },
116
+ to: { transform: 'translateX(0)' },
117
+ },
118
+ 'slide-in-from-right': {
119
+ from: { transform: 'translateX(100%)' },
120
+ to: { transform: 'translateX(0)' },
121
+ },
122
+ },
123
+ animation: {
124
+ 'accordion-down': 'accordion-down 0.2s ease-out',
125
+ 'accordion-up': 'accordion-up 0.2s ease-out',
126
+ 'fade-in': 'fade-in 0.2s ease-out',
127
+ 'fade-out': 'fade-out 0.2s ease-out',
128
+ 'slide-in-from-top': 'slide-in-from-top 0.25s ease-out',
129
+ 'slide-in-from-bottom': 'slide-in-from-bottom 0.25s ease-out',
130
+ 'slide-in-from-left': 'slide-in-from-left 0.25s ease-out',
131
+ 'slide-in-from-right': 'slide-in-from-right 0.25s ease-out',
132
+ },
133
+ },
134
+ },
135
+ plugins: [baseStylesPlugin],
136
+ future: {
137
+ hoverOnlyWhenSupported: true,
138
+ },
139
+ }
package/src/tokens.css ADDED
@@ -0,0 +1,66 @@
1
+ /* Pure CSS tokens - no Tailwind dependencies */
2
+
3
+ :root {
4
+ /* Base colors */
5
+ --color-background: 0 0% 100%; /* white */
6
+ --color-foreground: 222 47% 11%; /* slate-900 */
7
+
8
+ /* Card */
9
+ --color-card: 0 0% 100%; /* white */
10
+ --color-card-foreground: 222 47% 11%; /* slate-900 */
11
+
12
+ /* Primary - Default neutral theme */
13
+ --color-primary: 222 47% 11%; /* slate-900 */
14
+ --color-primary-foreground: 0 0% 100%; /* white */
15
+
16
+ /* Secondary */
17
+ --color-secondary: 210 40% 96%; /* slate-100 */
18
+ --color-secondary-foreground: 222 47% 11%; /* slate-900 */
19
+
20
+ /* Muted */
21
+ --color-muted: 210 40% 96%; /* slate-100 */
22
+ --color-muted-foreground: 215 16% 47%; /* slate-500 */
23
+
24
+ /* Accent */
25
+ --color-accent: 210 40% 96%; /* slate-100 */
26
+ --color-accent-foreground: 222 47% 11%; /* slate-900 */
27
+
28
+ /* Destructive */
29
+ --color-destructive: 0 84% 60%; /* red-500 */
30
+ --color-destructive-foreground: 0 0% 98%; /* white */
31
+
32
+ /* Border */
33
+ --color-border: 214 32% 91%; /* slate-200 */
34
+ --color-input: 214 32% 91%; /* slate-200 */
35
+ --color-ring: 222 47% 11%; /* slate-900 */
36
+
37
+ /* Radius */
38
+ --radius: 1rem; /* rounded-2xl equivalent */
39
+ }
40
+
41
+ .dark {
42
+ --color-background: 222 47% 11%; /* slate-900 */
43
+ --color-foreground: 210 40% 98%; /* slate-50 */
44
+
45
+ --color-card: 222 47% 11%; /* slate-900 */
46
+ --color-card-foreground: 210 40% 98%; /* slate-50 */
47
+
48
+ --color-primary: 210 40% 98%; /* slate-50 for dark mode */
49
+ --color-primary-foreground: 222 47% 11%; /* slate-900 */
50
+
51
+ --color-secondary: 217 33% 17%; /* slate-800 */
52
+ --color-secondary-foreground: 210 40% 98%; /* slate-50 */
53
+
54
+ --color-muted: 217 33% 17%; /* slate-800 */
55
+ --color-muted-foreground: 215 20% 65%; /* slate-400 */
56
+
57
+ --color-accent: 217 33% 17%; /* slate-800 */
58
+ --color-accent-foreground: 210 40% 98%; /* slate-50 */
59
+
60
+ --color-destructive: 0 63% 31%; /* dark red */
61
+ --color-destructive-foreground: 210 40% 98%; /* slate-50 */
62
+
63
+ --color-border: 217 33% 17%; /* slate-800 */
64
+ --color-input: 217 33% 17%; /* slate-800 */
65
+ --color-ring: 210 40% 98%; /* slate-50 */
66
+ }