esm-styles 0.3.0 → 0.3.1

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.
@@ -0,0 +1,793 @@
1
+ # ESM Styles Best Practices
2
+
3
+ This document outlines recommended practices and common pitfalls when using ESM Styles to help you write maintainable and efficient CSS-in-JS code.
4
+
5
+ ## Table of Contents
6
+
7
+ - [File Organization](#file-organization)
8
+ - [Naming Conventions](#naming-conventions)
9
+ - [Configuration](#configuration)
10
+ - [Style Structure](#style-structure)
11
+ - [CSS Variables](#css-variables)
12
+ - [Media Queries](#media-queries)
13
+ - [Performance](#performance)
14
+ - [Common Pitfalls](#common-pitfalls)
15
+
16
+ ## File Organization
17
+
18
+ ### ✅ DO: Use a clear directory structure
19
+
20
+ ```
21
+ src/styles/
22
+ ├── source/
23
+ │ ├── $theme.mjs # Generated theme variables
24
+ │ ├── $device.mjs # Generated device variables
25
+ │ ├── global.styles.mjs # Global CSS variables
26
+ │ ├── light.styles.mjs # Light theme variables
27
+ │ ├── dark.styles.mjs # Dark theme variables
28
+ │ ├── twilight.styles.mjs # Twilight theme variables
29
+ │ ├── phone.styles.mjs # Phone device variables
30
+ │ ├── tablet.styles.mjs # Tablet device variables
31
+ │ ├── notebook.styles.mjs # Notebook device variables
32
+ │ ├── defaults.styles.mjs # Reset, base styles
33
+ │ ├── components.styles.mjs # Component styles
34
+ │ ├── layout.styles.mjs # Layout styles
35
+ │ └── components/
36
+ │ ├── button.styles.mjs
37
+ │ ├── card.styles.mjs
38
+ │ └── modal.styles.mjs
39
+ ```
40
+
41
+ ### ✅ DO: Use consistent file naming
42
+
43
+ - Use kebab-case for module files: `button-group.styles.mjs`
44
+ - Use descriptive names: `navigation-menu.styles.mjs` not `nav.styles.mjs`
45
+ - Group related components in subdirectories
46
+
47
+ ### ❌ DON'T: Mix different concerns in one file
48
+
49
+ ```js
50
+ // ❌ BAD: mixing layout and components
51
+ export default {
52
+ // Layout styles
53
+ container: { maxWidth: '1200px' },
54
+
55
+ // Component styles
56
+ button: { padding: '10px' },
57
+
58
+ // Theme variables
59
+ colors: { primary: '#blue' },
60
+ }
61
+ ```
62
+
63
+ ## Naming Conventions
64
+
65
+ ### ✅ DO: Use semantic class names
66
+
67
+ ```js
68
+ // ✅ GOOD
69
+ export default {
70
+ navigationMenu: {
71
+ display: 'flex',
72
+
73
+ menuItem: {
74
+ padding: '8px 16px',
75
+ },
76
+
77
+ activeItem: {
78
+ fontWeight: 'bold',
79
+ },
80
+ },
81
+ }
82
+ ```
83
+
84
+ ### ❌ DON'T: Use presentation-based names
85
+
86
+ ```js
87
+ // ❌ BAD
88
+ export default {
89
+ blueBox: {
90
+ backgroundColor: 'blue', // What if you change to red?
91
+ },
92
+
93
+ bigText: {
94
+ fontSize: '24px', // What if you need different sizes?
95
+ },
96
+ }
97
+ ```
98
+
99
+ ### ✅ DO: Use consistent naming patterns
100
+
101
+ ```js
102
+ // ✅ GOOD: consistent modifier patterns
103
+ export default {
104
+ button: {
105
+ padding: '10px 20px',
106
+
107
+ // State modifiers
108
+ isDisabled: { opacity: 0.5 },
109
+ isLoading: { cursor: 'wait' },
110
+
111
+ // Size variants
112
+ sizeSmall: { padding: '5px 10px' },
113
+ sizeLarge: { padding: '15px 30px' },
114
+
115
+ // Style variants
116
+ variantPrimary: { backgroundColor: 'blue' },
117
+ variantSecondary: { backgroundColor: 'gray' },
118
+ },
119
+ }
120
+ ```
121
+
122
+ ### ❌ DON'T: Use ampersand (&) syntax
123
+
124
+ ```js
125
+ // ❌ BAD: ampersand is not supported
126
+ export default {
127
+ button: {
128
+ color: 'blue',
129
+
130
+ '&:hover': { // This won't work!
131
+ color: 'red'
132
+ },
133
+
134
+ '&.active': { // This won't work!
135
+ fontWeight: 'bold'
136
+ }
137
+ }
138
+ }
139
+
140
+ // ✅ GOOD: use direct selectors
141
+ export default {
142
+ button: {
143
+ color: 'blue',
144
+
145
+ ':hover': { // Direct pseudo-class
146
+ color: 'red'
147
+ },
148
+
149
+ active: { // Class name (if not HTML tag)
150
+ fontWeight: 'bold'
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ### ❌ DON'T: Use BEM-like naming
157
+
158
+ ```js
159
+ // ❌ BAD: BEM-style naming
160
+ export default {
161
+ card: {
162
+ padding: '20px',
163
+
164
+ card__header: { // Avoid block__element
165
+ marginBottom: '16px'
166
+ },
167
+
168
+ card__title: { // Repetitive naming
169
+ fontSize: '1.5rem'
170
+ },
171
+
172
+ 'card--featured': { // Avoid block--modifier
173
+ border: '2px solid gold'
174
+ }
175
+ }
176
+ }
177
+
178
+ // ✅ GOOD: semantic nesting
179
+ export default {
180
+ card: {
181
+ padding: '20px',
182
+
183
+ header: { // Simple, semantic
184
+ marginBottom: '16px',
185
+
186
+ title: { // Nested naturally
187
+ fontSize: '1.5rem'
188
+ }
189
+ },
190
+
191
+ featured: { // Clear modifier
192
+ border: '2px solid gold'
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### ❌ DON'T: Use dashes in class names
199
+
200
+ ```js
201
+ // ❌ BAD: dashes require quotes in JavaScript
202
+ export default {
203
+ 'navigation-menu': { // Needs quotes
204
+ display: 'flex'
205
+ },
206
+
207
+ 'user-profile': { // Harder to work with in JS
208
+ padding: '20px'
209
+ }
210
+ }
211
+
212
+ // ✅ GOOD: use camelCase for easier JS handling
213
+ export default {
214
+ navigationMenu: { // No quotes needed
215
+ display: 'flex'
216
+ },
217
+
218
+ userProfile: { // Easy to reference in JS
219
+ padding: '20px'
220
+ }
221
+ }
222
+ ```
223
+
224
+ ## Configuration
225
+
226
+ ### ✅ DO: Order floors logically
227
+
228
+ ```js
229
+ // ✅ GOOD: logical cascade order
230
+ floors: [
231
+ { source: 'defaults', layer: 'defaults' }, // Reset, base styles
232
+ { source: 'components', layer: 'components' }, // Component styles
233
+ { source: 'layout', layer: 'layout' }, // Layout styles
234
+ { source: 'utilities', layer: 'utilities' }, // Utility classes
235
+ { source: 'overrides' }, // High-specificity overrides
236
+ ]
237
+ ```
238
+
239
+ ### ✅ DO: Use meaningful breakpoint names
240
+
241
+ ```js
242
+ // ✅ GOOD: semantic breakpoints
243
+ const breakpoints = {
244
+ mobile: 499,
245
+ tablet: 1024,
246
+ desktop: 1440,
247
+ wide: 1920,
248
+ }
249
+ ```
250
+
251
+ ### ❌ DON'T: Use arbitrary breakpoint names
252
+
253
+ ```js
254
+ // ❌ BAD: meaningless names
255
+ const breakpoints = {
256
+ sm: 499,
257
+ md: 1024,
258
+ lg: 1440,
259
+ xl: 1920,
260
+ }
261
+ ```
262
+
263
+ ### ✅ DO: Group related media types
264
+
265
+ ```js
266
+ // ✅ GOOD: logical grouping
267
+ media: {
268
+ theme: ['light', 'dark', 'high-contrast'],
269
+ device: ['mobile', 'tablet', 'desktop'],
270
+ preference: ['reduced-motion', 'high-contrast']
271
+ }
272
+ ```
273
+
274
+ ## Style Structure
275
+
276
+ ### ✅ DO: Use semantic HTML with logical nesting
277
+
278
+ ```js
279
+ // ✅ GOOD: semantic tags with meaningful structure
280
+ export default {
281
+ article: {
282
+ padding: '20px',
283
+ borderRadius: '8px',
284
+
285
+ header: {
286
+ marginBottom: '16px',
287
+
288
+ h2: {
289
+ // Semantic heading tag
290
+ fontSize: '1.5rem',
291
+ fontWeight: 'bold',
292
+ },
293
+
294
+ time: {
295
+ // Semantic time element
296
+ fontSize: '0.9rem',
297
+ color: 'gray',
298
+ },
299
+ },
300
+
301
+ p: {
302
+ // Content in paragraphs
303
+ lineHeight: 1.6,
304
+ marginBottom: '1rem',
305
+ },
306
+
307
+ footer: {
308
+ marginTop: '16px',
309
+ textAlign: 'right',
310
+
311
+ button: {
312
+ // Semantic button
313
+ padding: '8px 16px',
314
+ },
315
+ },
316
+ },
317
+ }
318
+ ```
319
+
320
+ ### ✅ DO: Rely on semantic elements over generic divs
321
+
322
+ ```js
323
+ // ❌ BAD: everything is a div with classes
324
+ export default {
325
+ 'story-container': {
326
+ padding: '20px',
327
+
328
+ 'story-header': {
329
+ marginBottom: '16px',
330
+ },
331
+
332
+ 'story-title': {
333
+ fontSize: '1.5rem',
334
+ },
335
+
336
+ 'story-content': {
337
+ lineHeight: 1.6,
338
+ },
339
+
340
+ 'story-actions': {
341
+ marginTop: '16px',
342
+ },
343
+ },
344
+ }
345
+
346
+ // ✅ GOOD: semantic HTML structure
347
+ export default {
348
+ section: { // Semantic section
349
+ story: { // Custom component class
350
+ padding: '20px',
351
+
352
+ header: { // Semantic header
353
+ marginBottom: '16px',
354
+
355
+ h3: { // Proper heading hierarchy
356
+ fontSize: '1.5rem',
357
+ },
358
+ },
359
+
360
+ main: { // Main content area
361
+ lineHeight: 1.6,
362
+
363
+ p: { // Paragraphs for text
364
+ marginBottom: '1rem',
365
+ },
366
+ },
367
+
368
+ nav: { // Navigation for actions
369
+ marginTop: '16px',
370
+
371
+ button: { // Semantic buttons
372
+ marginRight: '8px',
373
+ },
374
+ },
375
+ },
376
+ },
377
+ }
378
+ ```
379
+
380
+ ### ❌ DON'T: Repeat class names or create redundant nesting
381
+
382
+ ```js
383
+ // ❌ BAD: repetitive naming and poor structure
384
+ export default {
385
+ 'story-list': {
386
+ 'story-item': {
387
+ 'story-item-content': {
388
+ 'story-item-title': { // Too repetitive!
389
+ fontSize: '1.2rem',
390
+ },
391
+
392
+ 'story-item-message': { // div.story div.story_message
393
+ padding: '10px',
394
+ },
395
+ },
396
+ },
397
+ },
398
+ }
399
+
400
+ // ✅ GOOD: semantic structure without repetition
401
+ export default {
402
+ section: {
403
+ story: { // section.story (semantic)
404
+ padding: '20px',
405
+
406
+ h3: { // story h3 (semantic heading)
407
+ fontSize: '1.2rem',
408
+ },
409
+
410
+ article: { // story article (semantic content)
411
+ message: { // story article.message
412
+ padding: '10px',
413
+ },
414
+ },
415
+ },
416
+ },
417
+ }
418
+ ```
419
+
420
+ ### ❌ DON'T: Over-nest selectors
421
+
422
+ ```js
423
+ // ❌ BAD: too deeply nested
424
+ export default {
425
+ page: {
426
+ main: {
427
+ section: {
428
+ article: {
429
+ div: {
430
+ p: {
431
+ span: {
432
+ color: 'red', // 7 levels deep!
433
+ },
434
+ },
435
+ },
436
+ },
437
+ },
438
+ },
439
+ },
440
+ }
441
+ ```
442
+
443
+ ### ✅ DO: Use composition for reusable styles
444
+
445
+ ```js
446
+ // ✅ GOOD: reusable patterns
447
+ const flexCenter = {
448
+ display: 'flex',
449
+ alignItems: 'center',
450
+ justifyContent: 'center',
451
+ }
452
+
453
+ const cardBase = {
454
+ padding: '20px',
455
+ borderRadius: '8px',
456
+ backgroundColor: 'white',
457
+ }
458
+
459
+ export default {
460
+ modal: {
461
+ ...flexCenter,
462
+ position: 'fixed',
463
+ inset: 0,
464
+ },
465
+
466
+ productCard: {
467
+ ...cardBase,
468
+ border: '1px solid #eee',
469
+ },
470
+
471
+ alertCard: {
472
+ ...cardBase,
473
+ border: '2px solid red',
474
+ },
475
+ }
476
+ ```
477
+
478
+ ## CSS Variables
479
+
480
+ ### ✅ DO: Use semantic variable names
481
+
482
+ ```js
483
+ // ✅ GOOD: semantic naming
484
+ export default {
485
+ colors: {
486
+ primary: '#4285f4',
487
+ secondary: '#34a853',
488
+ danger: '#ea4335',
489
+ surface: '#ffffff',
490
+ onSurface: '#000000',
491
+ },
492
+
493
+ spacing: {
494
+ unit: '8px',
495
+ small: '16px',
496
+ medium: '24px',
497
+ large: '32px',
498
+ },
499
+ }
500
+ ```
501
+
502
+ ### ✅ DO: Create consistent theme structures
503
+
504
+ ```js
505
+ // light.styles.mjs
506
+ export default {
507
+ surface: {
508
+ primary: '#ffffff',
509
+ secondary: '#f5f5f5',
510
+ accent: '#e3f2fd'
511
+ },
512
+ text: {
513
+ primary: '#212121',
514
+ secondary: '#757575',
515
+ disabled: '#bdbdbd'
516
+ }
517
+ }
518
+
519
+ // dark.styles.mjs
520
+ export default {
521
+ surface: {
522
+ primary: '#121212',
523
+ secondary: '#1e1e1e',
524
+ accent: '#263238'
525
+ },
526
+ text: {
527
+ primary: '#ffffff',
528
+ secondary: '#b3b3b3',
529
+ disabled: '#666666'
530
+ }
531
+ }
532
+ ```
533
+
534
+ ### ❌ DON'T: Hardcode theme-specific values in components
535
+
536
+ ```js
537
+ // ❌ BAD: hardcoded colors
538
+ export default {
539
+ button: {
540
+ backgroundColor: '#ffffff', // What about dark theme?
541
+ color: '#000000'
542
+ }
543
+ }
544
+
545
+ // ✅ GOOD: use theme variables
546
+ import $theme from './$theme.mjs'
547
+
548
+ export default {
549
+ button: {
550
+ backgroundColor: $theme.surface.primary,
551
+ color: $theme.text.primary
552
+ }
553
+ }
554
+ ```
555
+
556
+ ## Media Queries
557
+
558
+ ### ✅ DO: Use mobile-first approach
559
+
560
+ ```js
561
+ // ✅ GOOD: mobile-first
562
+ export default {
563
+ container: {
564
+ padding: '16px', // Mobile default
565
+
566
+ '@min-tablet': {
567
+ padding: '24px', // Tablet and up
568
+ },
569
+
570
+ '@min-desktop': {
571
+ padding: '32px', // Desktop and up
572
+ },
573
+ },
574
+ }
575
+ ```
576
+
577
+ ### ✅ DO: Use semantic media query names
578
+
579
+ ```js
580
+ // ✅ GOOD: descriptive names
581
+ mediaQueries: {
582
+ 'reduced-motion': '(prefers-reduced-motion: reduce)',
583
+ 'high-contrast': '(prefers-contrast: high)',
584
+ 'touch-device': '(hover: none) and (pointer: coarse)',
585
+ 'print': 'print'
586
+ }
587
+ ```
588
+
589
+ ### ❌ DON'T: Repeat media queries
590
+
591
+ ```js
592
+ // ❌ BAD: repeated media queries
593
+ export default {
594
+ header: {
595
+ '@media (max-width: 768px)': {
596
+ fontSize: '1.2rem'
597
+ }
598
+ },
599
+
600
+ nav: {
601
+ '@media (max-width: 768px)': { // Same breakpoint repeated
602
+ display: 'none'
603
+ }
604
+ }
605
+ }
606
+
607
+ // ✅ GOOD: use named queries
608
+ export default {
609
+ header: {
610
+ '@mobile': {
611
+ fontSize: '1.2rem'
612
+ }
613
+ },
614
+
615
+ nav: {
616
+ '@mobile': {
617
+ display: 'none'
618
+ }
619
+ }
620
+ }
621
+ ```
622
+
623
+ ## Performance
624
+
625
+ ### ✅ DO: Use efficient selectors
626
+
627
+ ```js
628
+ // ✅ GOOD: specific, efficient selectors
629
+ export default {
630
+ navigationMenu: {
631
+ display: 'flex',
632
+
633
+ menuItem: {
634
+ padding: '8px',
635
+ },
636
+ },
637
+ }
638
+ ```
639
+
640
+ ### ❌ DON'T: Use overly complex selectors
641
+
642
+ ```js
643
+ // ❌ BAD: complex, inefficient selectors
644
+ export default {
645
+ 'div > ul li:nth-child(odd) a[href*="example"]:not(.active)': {
646
+ color: 'red', // Too complex!
647
+ },
648
+ }
649
+ ```
650
+
651
+ ### ✅ DO: Minimize CSS output size
652
+
653
+ ```js
654
+ // ✅ GOOD: group similar styles
655
+ const buttonBase = {
656
+ padding: '10px 20px',
657
+ border: 'none',
658
+ borderRadius: '4px',
659
+ cursor: 'pointer',
660
+ }
661
+
662
+ export default {
663
+ primaryButton: {
664
+ ...buttonBase,
665
+ backgroundColor: 'blue',
666
+ color: 'white',
667
+ },
668
+
669
+ secondaryButton: {
670
+ ...buttonBase,
671
+ backgroundColor: 'gray',
672
+ color: 'black',
673
+ },
674
+ }
675
+ ```
676
+
677
+ ## Common Pitfalls
678
+
679
+ ### ❌ DON'T: Forget about CSS specificity
680
+
681
+ ```js
682
+ // ❌ PROBLEM: specificity conflicts
683
+ export default {
684
+ button: {
685
+ color: 'blue',
686
+
687
+ primary: {
688
+ color: 'white', // Might not override due to specificity
689
+ },
690
+ },
691
+ }
692
+
693
+ // ✅ SOLUTION: use layers or more specific selectors
694
+ floors: [
695
+ { source: 'base', layer: 'base' },
696
+ { source: 'components', layer: 'components' },
697
+ { source: 'overrides', layer: 'overrides' }, // Higher specificity layer
698
+ ]
699
+ ```
700
+
701
+ ### ❌ DON'T: Mix units inconsistently
702
+
703
+ ```js
704
+ // ❌ BAD: mixed units
705
+ export default {
706
+ container: {
707
+ padding: '16px',
708
+ margin: '1rem',
709
+ width: '50%',
710
+ height: '200pt' // Inconsistent!
711
+ }
712
+ }
713
+
714
+ // ✅ GOOD: consistent units
715
+ export default {
716
+ container: {
717
+ padding: '1rem',
718
+ margin: '1rem',
719
+ width: '50%',
720
+ height: '12.5rem' // Consistent rem units
721
+ }
722
+ }
723
+ ```
724
+
725
+ ### ❌ DON'T: Forget about accessibility
726
+
727
+ ```js
728
+ // ❌ BAD: ignores accessibility
729
+ export default {
730
+ button: {
731
+ backgroundColor: '#ff0000',
732
+ color: '#ff9999' // Poor contrast!
733
+ }
734
+ }
735
+
736
+ // ✅ GOOD: considers accessibility
737
+ export default {
738
+ button: {
739
+ backgroundColor: '#d32f2f',
740
+ color: '#ffffff', // Good contrast
741
+ fontSize: '16px', // Large enough for readability
742
+ padding: '12px 24px', // Adequate touch target size
743
+
744
+ '@reduced-motion': {
745
+ transition: 'none' // Respects user preferences
746
+ }
747
+ }
748
+ }
749
+ ```
750
+
751
+ ### ❌ DON'T: Hardcode magic numbers
752
+
753
+ ```js
754
+ // ❌ BAD: magic numbers
755
+ export default {
756
+ modal: {
757
+ zIndex: 9999, // Why 9999?
758
+ top: '73px' // Why 73px?
759
+ }
760
+ }
761
+
762
+ // ✅ GOOD: use meaningful variables
763
+ const zIndexes = {
764
+ modal: 1000,
765
+ tooltip: 1100,
766
+ dropdown: 1200
767
+ }
768
+
769
+ const headerHeight = '72px'
770
+
771
+ export default {
772
+ modal: {
773
+ zIndex: zIndexes.modal,
774
+ top: `calc(${headerHeight} + 1px)`
775
+ }
776
+ }
777
+ ```
778
+
779
+ ## Summary
780
+
781
+ - **Organize**: Use clear file structure and consistent naming
782
+ - **Naming**: Use camelCase, avoid BEM, avoid dashes, prefer semantic names
783
+ - **Structure**: Rely on semantic HTML tags over generic divs and classes
784
+ - **Nesting**: Use logical nesting, avoid repetitive class names
785
+ - **Syntax**: No ampersand (&) syntax, use direct selectors and pseudo-classes
786
+ - **Compose**: Reuse common patterns and avoid duplication
787
+ - **Configure**: Set up logical floors and meaningful breakpoints
788
+ - **Variables**: Use semantic names and consistent theme structures
789
+ - **Performance**: Write efficient selectors and minimize output
790
+ - **Accessibility**: Consider contrast, motion preferences, and usability
791
+ - **Maintainability**: Avoid magic numbers and overly complex selectors
792
+
793
+ Following these practices will help you create maintainable, performant, and accessible stylesheets with ESM Styles.