esm-styles 0.3.0 → 0.3.2

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,794 @@
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
+ border: `1px solid ${$theme.color.primary.var}` // in case of concatenation, use .var property of variable reference object
553
+ }
554
+ }
555
+ ```
556
+
557
+ ## Media Queries
558
+
559
+ ### ✅ DO: Use mobile-first approach
560
+
561
+ ```js
562
+ // ✅ GOOD: mobile-first
563
+ export default {
564
+ container: {
565
+ padding: '16px', // Mobile default
566
+
567
+ '@min-tablet': {
568
+ padding: '24px', // Tablet and up
569
+ },
570
+
571
+ '@min-desktop': {
572
+ padding: '32px', // Desktop and up
573
+ },
574
+ },
575
+ }
576
+ ```
577
+
578
+ ### ✅ DO: Use semantic media query names
579
+
580
+ ```js
581
+ // ✅ GOOD: descriptive names
582
+ mediaQueries: {
583
+ 'reduced-motion': '(prefers-reduced-motion: reduce)',
584
+ 'high-contrast': '(prefers-contrast: high)',
585
+ 'touch-device': '(hover: none) and (pointer: coarse)',
586
+ 'print': 'print'
587
+ }
588
+ ```
589
+
590
+ ### ❌ DON'T: Repeat media queries
591
+
592
+ ```js
593
+ // ❌ BAD: repeated media queries
594
+ export default {
595
+ header: {
596
+ '@media (max-width: 768px)': {
597
+ fontSize: '1.2rem'
598
+ }
599
+ },
600
+
601
+ nav: {
602
+ '@media (max-width: 768px)': { // Same breakpoint repeated
603
+ display: 'none'
604
+ }
605
+ }
606
+ }
607
+
608
+ // ✅ GOOD: use named queries
609
+ export default {
610
+ header: {
611
+ '@mobile': {
612
+ fontSize: '1.2rem'
613
+ }
614
+ },
615
+
616
+ nav: {
617
+ '@mobile': {
618
+ display: 'none'
619
+ }
620
+ }
621
+ }
622
+ ```
623
+
624
+ ## Performance
625
+
626
+ ### ✅ DO: Use efficient selectors
627
+
628
+ ```js
629
+ // ✅ GOOD: specific, efficient selectors
630
+ export default {
631
+ navigationMenu: {
632
+ display: 'flex',
633
+
634
+ menuItem: {
635
+ padding: '8px',
636
+ },
637
+ },
638
+ }
639
+ ```
640
+
641
+ ### ❌ DON'T: Use overly complex selectors
642
+
643
+ ```js
644
+ // ❌ BAD: complex, inefficient selectors
645
+ export default {
646
+ 'div > ul li:nth-child(odd) a[href*="example"]:not(.active)': {
647
+ color: 'red', // Too complex!
648
+ },
649
+ }
650
+ ```
651
+
652
+ ### ✅ DO: Minimize CSS output size
653
+
654
+ ```js
655
+ // ✅ GOOD: group similar styles
656
+ const buttonBase = {
657
+ padding: '10px 20px',
658
+ border: 'none',
659
+ borderRadius: '4px',
660
+ cursor: 'pointer',
661
+ }
662
+
663
+ export default {
664
+ primaryButton: {
665
+ ...buttonBase,
666
+ backgroundColor: 'blue',
667
+ color: 'white',
668
+ },
669
+
670
+ secondaryButton: {
671
+ ...buttonBase,
672
+ backgroundColor: 'gray',
673
+ color: 'black',
674
+ },
675
+ }
676
+ ```
677
+
678
+ ## Common Pitfalls
679
+
680
+ ### ❌ DON'T: Forget about CSS specificity
681
+
682
+ ```js
683
+ // ❌ PROBLEM: specificity conflicts
684
+ export default {
685
+ button: {
686
+ color: 'blue',
687
+
688
+ primary: {
689
+ color: 'white', // Might not override due to specificity
690
+ },
691
+ },
692
+ }
693
+
694
+ // ✅ SOLUTION: use layers or more specific selectors
695
+ floors: [
696
+ { source: 'base', layer: 'base' },
697
+ { source: 'components', layer: 'components' },
698
+ { source: 'overrides', layer: 'overrides' }, // Higher specificity layer
699
+ ]
700
+ ```
701
+
702
+ ### ❌ DON'T: Mix units inconsistently
703
+
704
+ ```js
705
+ // ❌ BAD: mixed units
706
+ export default {
707
+ container: {
708
+ padding: '16px',
709
+ margin: '1rem',
710
+ width: '50%',
711
+ height: '200pt' // Inconsistent!
712
+ }
713
+ }
714
+
715
+ // ✅ GOOD: consistent units
716
+ export default {
717
+ container: {
718
+ padding: '1rem',
719
+ margin: '1rem',
720
+ width: '50%',
721
+ height: '12.5rem' // Consistent rem units
722
+ }
723
+ }
724
+ ```
725
+
726
+ ### ❌ DON'T: Forget about accessibility
727
+
728
+ ```js
729
+ // ❌ BAD: ignores accessibility
730
+ export default {
731
+ button: {
732
+ backgroundColor: '#ff0000',
733
+ color: '#ff9999' // Poor contrast!
734
+ }
735
+ }
736
+
737
+ // ✅ GOOD: considers accessibility
738
+ export default {
739
+ button: {
740
+ backgroundColor: '#d32f2f',
741
+ color: '#ffffff', // Good contrast
742
+ fontSize: '16px', // Large enough for readability
743
+ padding: '12px 24px', // Adequate touch target size
744
+
745
+ '@reduced-motion': {
746
+ transition: 'none' // Respects user preferences
747
+ }
748
+ }
749
+ }
750
+ ```
751
+
752
+ ### ❌ DON'T: Hardcode magic numbers
753
+
754
+ ```js
755
+ // ❌ BAD: magic numbers
756
+ export default {
757
+ modal: {
758
+ zIndex: 9999, // Why 9999?
759
+ top: '73px' // Why 73px?
760
+ }
761
+ }
762
+
763
+ // ✅ GOOD: use meaningful variables
764
+ const zIndexes = {
765
+ modal: 1000,
766
+ tooltip: 1100,
767
+ dropdown: 1200
768
+ }
769
+
770
+ const headerHeight = '72px'
771
+
772
+ export default {
773
+ modal: {
774
+ zIndex: zIndexes.modal,
775
+ top: `calc(${headerHeight} + 1px)`
776
+ }
777
+ }
778
+ ```
779
+
780
+ ## Summary
781
+
782
+ - **Organize**: Use clear file structure and consistent naming
783
+ - **Naming**: Use camelCase, avoid BEM, avoid dashes, prefer semantic names
784
+ - **Structure**: Rely on semantic HTML tags over generic divs and classes
785
+ - **Nesting**: Use logical nesting, avoid repetitive class names
786
+ - **Syntax**: No ampersand (&) syntax, use direct selectors and pseudo-classes
787
+ - **Compose**: Reuse common patterns and avoid duplication
788
+ - **Configure**: Set up logical floors and meaningful breakpoints
789
+ - **Variables**: Use semantic names and consistent theme structures
790
+ - **Performance**: Write efficient selectors and minimize output
791
+ - **Accessibility**: Consider contrast, motion preferences, and usability
792
+ - **Maintainability**: Avoid magic numbers and overly complex selectors
793
+
794
+ Following these practices will help you create maintainable, performant, and accessible stylesheets with ESM Styles.