@dcl/asset-packs 2.7.2-20251121153532.commit-10a35d7 → 2.7.2-20251208165801.commit-7c106da

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,849 @@
1
+ ---
2
+ description: "Standards for styled-components in Decentraland projects."
3
+ globs: src/**/*.styled.ts,src/**/*.styled.tsx
4
+ alwaysApply: true
5
+ ---
6
+
7
+ # Styled Components
8
+
9
+ ## Scope
10
+
11
+ These standards MUST be applied to all files that match the following patterns, always:
12
+
13
+ - `src/**/*.styled.ts` - TypeScript styled component files
14
+ - `src/**/*.styled.tsx` - TypeScript styled component files with JSX
15
+
16
+ > **Note:** Files must use the `.styled.ts` or `.styled.tsx` naming convention. Other naming patterns are not supported.
17
+
18
+ ## Introduction
19
+
20
+ This document describes the standards for styled components in our codebase. These standards ensure consistency and maintainability across our UI components.
21
+
22
+ ## Theme Structure
23
+
24
+ The theme in `decentraland-ui2` is built on top of Material-UI's theme system. Understanding the theme structure is crucial for writing consistent styled components.
25
+
26
+ ### Theme Source
27
+
28
+ The theme is defined in the `decentraland-ui2` package and follows Material-UI's theme structure. The exact implementation may vary, but the theme provides consistent design tokens across the application.
29
+
30
+ **Reference**: The complete theme configuration can be found in the [decentraland/ui2 repository](https://github.com/decentraland/ui2/tree/master/src/theme), specifically in the [theme index file](https://github.com/decentraland/ui2/blob/master/src/theme/index.ts).
31
+
32
+ **Additional Theme Files**:
33
+
34
+ - [Color Schemes](https://github.com/decentraland/ui2/blob/master/src/theme/colorSchemes.ts) - Defines the color palette for light and dark themes
35
+ - [Typography](https://github.com/decentraland/ui2/blob/master/src/theme/typography.ts) - Defines font families, sizes, and typography variants
36
+
37
+ ### Available Theme Properties
38
+
39
+ The theme provides the following design tokens that should be used instead of hardcoded values. For the most up-to-date values, refer to the [theme configuration in the repository](https://github.com/decentraland/ui2/blob/master/src/theme/index.ts):
40
+
41
+ 1. **Palette**: Colors for text, background, primary, secondary, etc.
42
+
43
+ - `theme.palette.text.primary` - Main text color
44
+ - `theme.palette.text.secondary` - Secondary text color
45
+ - `theme.palette.background.default` - Default background color
46
+ - `theme.palette.primary.main` - Primary color
47
+ - `theme.palette.secondary.main` - Secondary color
48
+
49
+ **Reference**: See [colorSchemes.ts](https://github.com/decentraland/ui2/blob/master/src/theme/colorSchemes.ts) for the complete color palette definition.
50
+
51
+ 2. **Typography**: Predefined text styles
52
+
53
+ - `theme.typography.h1` through `theme.typography.h6` - Headings
54
+ - `theme.typography.body1`, `theme.typography.body2` - Body text
55
+ - `theme.typography.caption` - Caption text
56
+ - `theme.typography.button` - Button text
57
+
58
+ **Reference**: See [typography.ts](https://github.com/decentraland/ui2/blob/master/src/theme/typography.ts) for font families, sizes, and typography variants.
59
+
60
+ 3. **Spacing**: Consistent spacing units (base multiplier of 4px)
61
+
62
+ - `theme.spacing(1)` - Small spacing unit (4px)
63
+ - `theme.spacing(2)` - Medium spacing unit (8px)
64
+ - `theme.spacing(3)` - Large spacing unit (12px)
65
+ - `theme.spacing(4)` - Extra large spacing unit (16px)
66
+
67
+ **Reference**: The spacing system follows Material-UI's default spacing function with a base of 4px. For the exact implementation, see [Material-UI spacing documentation](https://mui.com/material-ui/customization/spacing/) and check the spacing configuration in the [theme index file](https://github.com/decentraland/ui2/blob/master/src/theme/index.ts).
68
+
69
+ 4. **Breakpoints**: Responsive design breakpoints
70
+
71
+ **Breakpoint Values:**
72
+
73
+ - `xs`: 768px (mobile)
74
+ - `sm`: 991px (tablet)
75
+ - `md`: 1024px (desktop)
76
+ - `lg`: 1280px (large desktop)
77
+ - `xl`: 1500px (extra large desktop)
78
+
79
+ **Usage Examples:**
80
+
81
+ - `theme.breakpoints.down("sm")` - Below 991px
82
+ - `theme.breakpoints.up("md")` - Above 1024px
83
+ - `theme.breakpoints.between("sm", "lg")` - Between 991px and 1280px
84
+ - `theme.breakpoints.only("md")` - Only between 1024px and 1280px
85
+
86
+ 5. **Shape**: Common shape properties
87
+ - `theme.shape.borderRadius` - Default border radius
88
+
89
+ **Note**: The exact breakpoint values and other theme properties are defined in the [theme configuration](https://github.com/decentraland/ui2/blob/master/src/theme/index.ts). Always refer to the source for the most current values.
90
+
91
+ ### Theme Usage Examples
92
+
93
+ ```typescript
94
+ // Using palette colors
95
+ const Title = styled(Typography)(({ theme }) => ({
96
+ color: theme.palette.text.primary, // Main text color
97
+ backgroundColor: theme.palette.background.default,
98
+ }));
99
+
100
+ // Using typography variants
101
+ const Heading = styled(Typography)(({ theme }) => ({
102
+ ...theme.typography.h3, // Predefined h3 styles
103
+ }));
104
+
105
+ // Using spacing units
106
+ const Container = styled(Box)(({ theme }) => ({
107
+ padding: theme.spacing(3), // Consistent spacing
108
+ margin: theme.spacing(2), // Consistent spacing
109
+ }));
110
+
111
+ // Using breakpoints
112
+ const ResponsiveBox = styled(Box)(({ theme }) => ({
113
+ [theme.breakpoints.down("sm")]: {
114
+ // Responsive behavior (below 991px)
115
+ flexDirection: "column",
116
+ },
117
+ }));
118
+
119
+ // Using shape properties
120
+ const RoundedCard = styled(Box)(({ theme }) => ({
121
+ borderRadius: theme.shape.borderRadius, // Consistent border radius
122
+ }));
123
+ ```
124
+
125
+ ## Import Standards
126
+
127
+ ### Import Sources
128
+
129
+ 1. MUST use styled components from `decentraland-ui2`:
130
+
131
+ ```typescript
132
+ // ❌ Wrong
133
+ import styled from "@emotion/styled";
134
+ import { keyframes } from "@emotion/react";
135
+
136
+ // ✅ Correct
137
+ import { styled, keyframes } from "decentraland-ui2";
138
+ ```
139
+
140
+ 2. MUST import MUI components from `decentraland-ui2`:
141
+
142
+ ```typescript
143
+ // ❌ Wrong
144
+ import { Box } from "@mui/material";
145
+
146
+ // ✅ Correct
147
+ import { Box } from "decentraland-ui2";
148
+ ```
149
+
150
+ 3. MUST order imports alphabetically according to ESLint rules:
151
+
152
+ ```typescript
153
+ // ❌ Wrong
154
+ import { Modal, Box, styled, keyframes } from "decentraland-ui2";
155
+
156
+ // ✅ Correct
157
+ import { Box, keyframes, Modal, styled } from "decentraland-ui2";
158
+ ```
159
+
160
+ 4. MUST follow ESLint import order rules:
161
+
162
+ ```typescript
163
+ // ✅ Correct import order
164
+ import { Box, Button, styled, Typography } from "decentraland-ui2";
165
+ import { neutral } from "../../theme/colors";
166
+ import { Modal } from "../Modal/Modal";
167
+ ```
168
+
169
+ ## Component Definition
170
+
171
+ ### Styled Component Structure
172
+
173
+ 1. MUST define all styled components as constants:
174
+
175
+ ```typescript
176
+ // ❌ Wrong
177
+ export const Container = styled(Box)({...});
178
+
179
+ // ✅ Correct
180
+ const Container = styled(Box)({...});
181
+ ```
182
+
183
+ ### File Extensions
184
+
185
+ 1. SHOULD use `.styled.ts` for TypeScript styled component files:
186
+
187
+ ```
188
+ // ✅ Correct
189
+ Component.styled.ts
190
+ Component.tsx
191
+ ```
192
+
193
+ 2. MAY use `.styled.tsx` when styled components include JSX elements:
194
+
195
+ ```
196
+ // ✅ Acceptable for complex styled components
197
+ Component.styled.tsx
198
+ ```
199
+
200
+ 3. MUST group all exports at the end of the file:
201
+
202
+ ```typescript
203
+ // ❌ Wrong
204
+ export const Container = styled(Box)({...});
205
+ export const Title = styled(Typography)({...});
206
+ export const Image = styled("img")({...});
207
+
208
+ // ✅ Correct
209
+ const Container = styled(Box)({...});
210
+ const Title = styled(Typography)({...});
211
+ const Image = styled("img")({...});
212
+
213
+ export {
214
+ Container,
215
+ Image,
216
+ Title,
217
+ };
218
+ ```
219
+
220
+ 4. MUST order exports alphabetically:
221
+
222
+ ```typescript
223
+ // ❌ Wrong
224
+ export {
225
+ RewardReachedModal,
226
+ RewardContainer,
227
+ AnimatedBackground,
228
+ RewardImage,
229
+ };
230
+
231
+ // ✅ Correct
232
+ export {
233
+ AnimatedBackground,
234
+ RewardContainer,
235
+ RewardImage,
236
+ RewardReachedModal,
237
+ };
238
+ ```
239
+
240
+ 5. MUST NOT use comments in styled component files:
241
+
242
+ ```typescript
243
+ // ❌ Wrong
244
+ /**
245
+ * Component that displays a card with a gradient border
246
+ */
247
+ const Card = styled(Box)({...});
248
+
249
+ // Layout styles
250
+ const Container = styled(Box)({
251
+ // Position
252
+ position: "relative",
253
+ // Display
254
+ display: "flex",
255
+ });
256
+
257
+ // ✅ Correct
258
+ const Card = styled(Box)({...});
259
+
260
+ const Container = styled(Box)({
261
+ position: "relative",
262
+ display: "flex",
263
+ });
264
+ ```
265
+
266
+ ## Styling Patterns
267
+
268
+ ### Object Syntax Standard
269
+
270
+ UI2 components MUST use the object syntax. This ensures strong TypeScript support, csstype validation, and direct theme integration. Avoid the template literal syntax.
271
+
272
+ ```typescript
273
+ // ❌ Wrong
274
+ const Button = styled("button")`
275
+ color: turquoise;
276
+ `;
277
+
278
+ // ✅ Correct
279
+ const Button = styled("button")({
280
+ color: "turquoise",
281
+ });
282
+ ```
283
+
284
+ With theme and props:
285
+
286
+ ```typescript
287
+ interface Props {
288
+ primary?: boolean;
289
+ }
290
+ const Button = styled("button")<Props>(({ theme, primary }) => ({
291
+ color: primary ? theme.palette.primary.main : theme.palette.text.primary,
292
+ borderRadius: theme.shape.borderRadius,
293
+ }));
294
+ ```
295
+
296
+ ⚠️ Rule: Values must always come from the UI2 theme. Arbitrary hex codes or pixel values are not allowed.
297
+
298
+ ### Allowed Element Syntax
299
+
300
+ When styling native HTML elements, always use the function call form `styled('tag')`. Do not use the property form `styled.tag`, which is legacy syntax and conflicts with our object-style standard.
301
+
302
+ ```typescript
303
+ // ❌ Wrong
304
+ const Container = styled.div`
305
+ display: flex;
306
+ `;
307
+
308
+ // ✅ Correct
309
+ const Container = styled("div")({
310
+ display: "flex",
311
+ });
312
+ ```
313
+
314
+ Specific case: prefer `styled('button')` over `styled.button`
315
+
316
+ ```typescript
317
+ // ❌ Wrong
318
+ const Action = styled.button`
319
+ color: red;
320
+ `;
321
+
322
+ // ✅ Correct
323
+ const Action = styled("button")(({ theme }) => ({
324
+ color: theme.palette.text.primary,
325
+ }));
326
+ ```
327
+
328
+ ### Component Base
329
+
330
+ 1. MUST extend existing UI2 components when possible:
331
+
332
+ ```typescript
333
+ // ❌ Wrong
334
+ const Container = styled("div")({...});
335
+
336
+ // ✅ Correct
337
+ const Container = styled(Box)({...});
338
+ ```
339
+
340
+ 2. MUST use semantic HTML elements for basic elements:
341
+
342
+ ```typescript
343
+ // ❌ Wrong
344
+ const Image = styled("div")({...});
345
+
346
+ // ✅ Correct
347
+ const Image = styled("img")({...});
348
+ ```
349
+
350
+ ### Props and Types
351
+
352
+ 1. MUST type props for styled components that accept them:
353
+
354
+ ```typescript
355
+ // ❌ Wrong
356
+ const GradientBorder = styled(Box)(({ completed }) => ({...}));
357
+
358
+ // ✅ Correct
359
+ const GradientBorder = styled(Box)<{ completed: boolean }>(({ completed }) => ({...}));
360
+ ```
361
+
362
+ 2. MUST type custom properties that do not exist in the base component:
363
+
364
+ ```typescript
365
+ // ❌ Wrong
366
+ const Container = styled(Box)(({ theme, imageUrl }) => ({
367
+ backgroundImage: `url(${imageUrl})`,
368
+ }));
369
+
370
+ // ✅ Correct
371
+ const Container = styled(Box)<{ imageUrl: string }>(
372
+ ({ theme, imageUrl }) => ({
373
+ backgroundImage: `url(${imageUrl})`,
374
+ })
375
+ );
376
+ ```
377
+
378
+ 3. SHOULD use theme types when accessing theme properties:
379
+
380
+ ```typescript
381
+ // ❌ Wrong
382
+ const Title = styled(Typography)({
383
+ color: "#fff",
384
+ });
385
+
386
+ // ✅ Correct
387
+ const Title = styled(Typography)(({ theme }) => ({
388
+ color: theme.palette.text.primary,
389
+ }));
390
+ ```
391
+
392
+ ### Style Organization
393
+
394
+ 1. SHOULD group related styles together without comments:
395
+
396
+ ```typescript
397
+ // ❌ Wrong
398
+ const Step = styled(Box)(({ theme }) => ({
399
+ // Layout
400
+ display: "flex",
401
+ alignItems: "flex-start",
402
+
403
+ // Visual
404
+ background: "rgba(255, 255, 255, 0.1)",
405
+ borderRadius: "16px",
406
+
407
+ // Spacing
408
+ padding: "24px",
409
+ }));
410
+
411
+ // ✅ Correct
412
+ const Step = styled(Box)(({ theme }) => ({
413
+ display: "flex",
414
+ alignItems: "flex-start",
415
+ background: "rgba(255, 255, 255, 0.1)",
416
+ borderRadius: "16px",
417
+ padding: "24px",
418
+ }));
419
+ ```
420
+
421
+ 2. SHOULD use theme breakpoints for responsive design:
422
+
423
+ ```typescript
424
+ // ❌ Wrong
425
+ const Container = styled(Box)({
426
+ "@media (max-width: 768px)": {
427
+ flexDirection: "column",
428
+ },
429
+ });
430
+
431
+ // ✅ Correct
432
+ const Container = styled(Box)(({ theme }) => ({
433
+ [theme.breakpoints.down("sm")]: {
434
+ flexDirection: "column",
435
+ },
436
+ }));
437
+ ```
438
+
439
+ ### Common Patterns
440
+
441
+ 1. SHOULD use theme colors and values:
442
+
443
+ ```typescript
444
+ // ❌ Wrong
445
+ const Title = styled(Typography)({
446
+ color: "#fff",
447
+ fontSize: "16px",
448
+ });
449
+
450
+ // ✅ Correct
451
+ const Title = styled(Typography)(({ theme }) => ({
452
+ color: theme.palette.text.primary,
453
+ fontSize: theme.typography.body1.fontSize,
454
+ }));
455
+ ```
456
+
457
+ 2. SHOULD use theme spacing units:
458
+
459
+ ```typescript
460
+ // ❌ Wrong
461
+ const Container = styled(Box)({
462
+ padding: "24px",
463
+ margin: "16px",
464
+ });
465
+
466
+ // ✅ Correct
467
+ const Container = styled(Box)(({ theme }) => ({
468
+ padding: theme.spacing(3),
469
+ margin: theme.spacing(2),
470
+ }));
471
+ ```
472
+
473
+ 3. SHOULD use theme typography variants:
474
+
475
+ ```typescript
476
+ // ❌ Wrong
477
+ const Title = styled(Typography)({
478
+ fontSize: "24px",
479
+ fontWeight: 700,
480
+ lineHeight: "32px",
481
+ });
482
+
483
+ // ✅ Correct
484
+ const Title = styled(Typography)(({ theme }) => ({
485
+ ...theme.typography.h3,
486
+ }));
487
+ ```
488
+
489
+ ### Complex Styles
490
+
491
+ 1. MUST use object syntax with theme values for complex styles:
492
+
493
+ ```typescript
494
+ const GradientBorder = styled(Box)<{ completed: boolean }>(
495
+ ({ theme, completed }) => ({
496
+ background: completed
497
+ ? `linear-gradient(243.96deg, ${theme.palette.primary.main} -11.67%, ${theme.palette.secondary.main} 88.23%)`
498
+ : theme.palette.action.hover,
499
+ })
500
+ );
501
+ ```
502
+
503
+ 2. SHOULD use theme utilities for common patterns:
504
+
505
+ ```typescript
506
+ const Card = styled(Box)(({ theme }) => ({
507
+ display: "flex",
508
+ alignItems: "center",
509
+ justifyContent: "center",
510
+ padding: theme.spacing(2),
511
+ backgroundColor: theme.palette.background.paper,
512
+ borderRadius: theme.shape.borderRadius,
513
+ }));
514
+ ```
515
+
516
+ 3. MUST avoid !important declarations. Use better CSS specificity instead:
517
+
518
+ ```typescript
519
+ // ❌ Wrong
520
+ const StatLabel = styled(Typography)(({ theme }) => ({
521
+ "&&": {
522
+ color: "rgba(255, 255, 255, 0.75) !important",
523
+ },
524
+ }));
525
+
526
+ // ✅ Correct
527
+ const StatLabel = styled(Typography)(({ theme }) => ({
528
+ color: theme.palette.text.secondary,
529
+ opacity: 0.75,
530
+ }));
531
+ ```
532
+
533
+ ### No Inline Styles
534
+
535
+ Inline styles (style= ... ) MUST NOT be used in UI2 components. They bypass theme typing, make styles harder to maintain, and prevent reusability. If a style value needs to be dynamic, pass it as a typed prop to the styled component.
536
+
537
+ ```typescript
538
+ // ❌ Wrong
539
+ <Card key={id} style={{ backgroundColor: color } as React.CSSProperties}>
540
+ <CardHeader>
541
+ <Title style={{ fontSize: 20 }}>{title}</Title>
542
+ </CardHeader>
543
+ </Card>
544
+
545
+ // ✅ Correct
546
+ interface CardProps { color: string }
547
+ const Card = styled(Box)<CardProps>(({ color }) => ({ backgroundColor: color }))
548
+ const Title = styled('h2')<CardProps>(({ fontSize }) => ({ fontSize }))
549
+
550
+ <Card key={id} color={color}>
551
+ <CardHeader>
552
+ <Title fontSize={20}>{title}</Title>
553
+ </CardHeader>
554
+ </Card>
555
+ ```
556
+
557
+ ## Best Practices
558
+
559
+ ### Breakpoints
560
+
561
+ Use theme.breakpoints helpers instead of hardcoded pixel values. Always use up, down, between, or only.
562
+
563
+ ```typescript
564
+ // ❌ Wrong
565
+ const Panel = styled("div")`
566
+ @media (max-width: 768px) {
567
+ width: 100%;
568
+ }
569
+ `;
570
+
571
+ // ✅ Correct
572
+ interface PanelProps {
573
+ expanded: boolean;
574
+ }
575
+ const Panel = styled("div")<PanelProps>(({ theme, expanded }) => ({
576
+ width: expanded ? "400px" : "0",
577
+ [theme.breakpoints.down("sm")]: { width: expanded ? "100%" : "0" },
578
+ }));
579
+ ```
580
+
581
+ ### Spacing Scale and theme.spacing
582
+
583
+ Use theme.spacing exclusively for margins, paddings, and gaps.
584
+
585
+ - Convention: theme.spacing(n) where n is an integer. If the base is 4, 1 → 4px and 2 → 8px.
586
+ - Do not use raw px values. If a new value is needed, propose it in the theme.
587
+
588
+ ```typescript
589
+ // ✅ Correct
590
+ const Box = styled("div")(({ theme }) => ({
591
+ padding: theme.spacing(2),
592
+ gap: theme.spacing(3),
593
+ }));
594
+ ```
595
+
596
+ ### Z-index, elevation, and stacking context
597
+
598
+ Follow the theme scale: theme.zIndex.appBar, drawer, modal, tooltip. Avoid ad‑hoc z-index. If a new layer is needed, add it to the theme first. Manage stacking context consciously and avoid unnecessary position: relative on parents.
599
+
600
+ ```typescript
601
+ // ✅ Correct
602
+ const StickyBar = styled("div")(({ theme }) => ({
603
+ position: "sticky",
604
+ top: 0,
605
+ zIndex: theme.zIndex.appBar,
606
+ }));
607
+ ```
608
+
609
+ ### Focus visible and interactive states
610
+
611
+ All interactive controls MUST show a visible focus and declare hover, active, and disabled states.
612
+
613
+ ```typescript
614
+ // ✅ Correct
615
+ const Action = styled("button")(({ theme }) => ({
616
+ outline: "none",
617
+ "&:focus-visible": {
618
+ outline: `2px solid ${theme.palette.primary.main}`,
619
+ outlineOffset: 2,
620
+ },
621
+ "&:hover": { backgroundColor: theme.palette.action.hover },
622
+ "&:active": { backgroundColor: theme.palette.action.selected },
623
+ "&:disabled": { opacity: 0.5, pointerEvents: "none" },
624
+ }));
625
+ ```
626
+
627
+ ### Performance with styled
628
+
629
+ - Do not create styled components inside render paths.
630
+ - Memoize derived props that trigger restyling.
631
+ - Do not pass inline object props. Prefer primitive props and map them inside styled.
632
+
633
+ ```typescript
634
+ // ❌ Wrong
635
+ const MyComponent = ({ color, ...props }) => {
636
+ const StyledDiv = styled("div")(({ theme }) => ({ color })); // Created in render
637
+ return <StyledDiv style={{ backgroundColor: color }} {...props} />;
638
+ };
639
+
640
+ // ✅ Correct
641
+ const StyledDiv = styled("div")<{ color: string }>(({ color }) => ({ color }));
642
+
643
+ const MyComponent = ({ color, ...props }) => {
644
+ return <StyledDiv color={color} {...props} />;
645
+ };
646
+ ```
647
+
648
+ ### File Organization
649
+
650
+ 1. MUST keep styled components in separate files:
651
+
652
+ ```
653
+ Component.tsx
654
+ Component.styled.ts
655
+ ```
656
+
657
+ 2. MUST use meaningful names that reflect the component's purpose:
658
+
659
+ ```typescript
660
+ // ❌ Wrong
661
+ const Div = styled(Box)({...});
662
+ const Wrapper = styled(Box)({...});
663
+
664
+ // ✅ Correct
665
+ const CardContainer = styled(Box)({...});
666
+ const RewardImage = styled("img")({...});
667
+ ```
668
+
669
+ 3. MUST use consistent naming conventions:
670
+ - Use PascalCase for component names
671
+ - Use camelCase for style properties
672
+ - Use kebab-case for HTML attributes
673
+ ```typescript
674
+ const RewardCardContainer = styled(Box)(({ theme }) => ({
675
+ backgroundColor: theme.palette.background.paper,
676
+ "data-testid": "reward-card",
677
+ }));
678
+ ```
679
+
680
+ ## Examples
681
+
682
+ ### Basic Styled Component
683
+
684
+ ```typescript
685
+ import { Box, styled, Typography } from "decentraland-ui2";
686
+
687
+ const CardContainer = styled(Box)(({ theme }) => ({
688
+ display: "flex",
689
+ flexDirection: "column",
690
+ padding: theme.spacing(3),
691
+ backgroundColor: theme.palette.background.default,
692
+ borderRadius: theme.shape.borderRadius,
693
+ }));
694
+
695
+ const CardTitle = styled(Typography)(({ theme }) => ({
696
+ ...theme.typography.h4,
697
+ color: theme.palette.text.primary,
698
+ marginBottom: theme.spacing(2),
699
+ }));
700
+
701
+ const CardContent = styled(Box)(({ theme }) => ({
702
+ color: theme.palette.text.secondary,
703
+ ...theme.typography.body1,
704
+ }));
705
+
706
+ export { CardContainer, CardContent, CardTitle };
707
+ ```
708
+
709
+ ### Styled Component with Props
710
+
711
+ ```typescript
712
+ import { Box, styled } from "decentraland-ui2";
713
+
714
+ const GradientButton = styled(Box)<{ variant: "primary" | "secondary" }>(
715
+ ({ theme, variant }) => ({
716
+ display: "flex",
717
+ alignItems: "center",
718
+ justifyContent: "center",
719
+ padding: theme.spacing(2),
720
+ borderRadius: theme.shape.borderRadius,
721
+ cursor: "pointer",
722
+ backgroundColor:
723
+ variant === "primary"
724
+ ? theme.palette.primary.main
725
+ : theme.palette.secondary.main,
726
+ color: theme.palette.common.white,
727
+ "&:hover": {
728
+ opacity: 0.8,
729
+ },
730
+ })
731
+ );
732
+
733
+ const ResponsiveContainer = styled(Box)(({ theme }) => ({
734
+ maxWidth: "1200px",
735
+ margin: "0 auto",
736
+ padding: theme.spacing(3),
737
+ [theme.breakpoints.down("sm")]: {
738
+ padding: theme.spacing(2),
739
+ },
740
+ }));
741
+
742
+ export { GradientButton, ResponsiveContainer };
743
+ ```
744
+
745
+ ### Complex Styled Component
746
+
747
+ ```typescript
748
+ import { Box, styled } from "decentraland-ui2";
749
+
750
+ const AnimatedCard = styled(Box)<{ isVisible: boolean }>(
751
+ ({ theme, isVisible }) => ({
752
+ display: "flex",
753
+ flexDirection: "column",
754
+ padding: theme.spacing(3),
755
+ backgroundColor: theme.palette.background.paper,
756
+ borderRadius: theme.shape.borderRadius,
757
+ boxShadow: theme.shadows[1],
758
+ transform: isVisible ? "translateY(0)" : "translateY(20px)",
759
+ opacity: isVisible ? 1 : 0,
760
+ transition: "all 0.3s ease-in-out",
761
+ "&:hover": {
762
+ boxShadow: theme.shadows[4],
763
+ transform: "translateY(-2px)",
764
+ },
765
+ })
766
+ );
767
+
768
+ const StatusBadge = styled(Box)<{ status: "success" | "warning" | "error" }>(
769
+ ({ theme, status }) => ({
770
+ display: "inline-flex",
771
+ alignItems: "center",
772
+ padding: `${theme.spacing(0.5)} ${theme.spacing(1)}`,
773
+ borderRadius: theme.shape.borderRadius,
774
+ fontSize: theme.typography.caption.fontSize,
775
+ fontWeight: theme.typography.caption.fontWeight,
776
+ backgroundColor:
777
+ status === "success"
778
+ ? theme.palette.success.main
779
+ : status === "warning"
780
+ ? theme.palette.warning.main
781
+ : theme.palette.error.main,
782
+ color: theme.palette.common.white,
783
+ })
784
+ );
785
+
786
+ export { AnimatedCard, StatusBadge };
787
+ ```
788
+
789
+ ## Validation Checklist
790
+
791
+ Before creating styled components, ensure:
792
+
793
+ - [ ] MUST and SHOULD sections are clearly defined
794
+ - [ ] All imports are from `decentraland-ui2`
795
+ - [ ] All MUI components are imported from `decentraland-ui2`
796
+ - [ ] Styled components use `styled` from `decentraland-ui2`
797
+ - [ ] Imports are ordered alphabetically according to ESLint rules
798
+ - [ ] Object syntax is used for all styled components
799
+ - [ ] Template literal syntax is avoided
800
+ - [ ] Function call form `styled('tag')` is used for HTML elements
801
+ - [ ] No !important declarations are used
802
+ - [ ] CSS specificity is properly managed
803
+ - [ ] Inline styles are avoided
804
+ - [ ] No inline comments are used in styled component files
805
+ - [ ] Dynamic values are passed as typed props
806
+ - [ ] Styled components are defined as constants
807
+ - [ ] All exports are grouped at the end of the file
808
+ - [ ] Exports are ordered alphabetically
809
+ - [ ] Props are properly typed for styled components
810
+ - [ ] Theme properties are used instead of hardcoded values
811
+ - [ ] Theme breakpoints are used for responsive design
812
+ - [ ] Theme spacing units are used for consistent spacing
813
+ - [ ] Theme typography variants are used for text styles
814
+ - [ ] Z-index values come from theme.zIndex scale
815
+ - [ ] Interactive states (hover, focus, active, disabled) are implemented
816
+ - [ ] Focus-visible styles are included for accessibility
817
+ - [ ] Performance best practices are followed
818
+ - [ ] Styled components are not created in render paths
819
+ - [ ] Primitive props are preferred over object props
820
+ - [ ] Component names are meaningful and follow PascalCase
821
+ - [ ] Style properties use camelCase
822
+ - [ ] HTML attributes use kebab-case
823
+ - [ ] Theme colors use palette instead of arbitrary hex values
824
+ - [ ] Breakpoint helpers (up, down, between) are used instead of media queries
825
+ - [ ] File organization follows Component.tsx / Component.styled.ts pattern
826
+ - [ ] Specific code examples are included
827
+ - [ ] Incorrect (❌) and correct (✅) patterns are shown
828
+ - [ ] Rules are specific and actionable
829
+ - [ ] Scope clearly defines file patterns
830
+ - [ ] Description is concise and descriptive
831
+ - [ ] Glob patterns are appropriate for frontend
832
+ - [ ] Rules follow the established format and structure
833
+ - [ ] Content is comprehensive and well-organized
834
+ - [ ] Examples are relevant and demonstrate best practices
835
+ - [ ] Theme integration is properly implemented
836
+ - [ ] Responsive design patterns are followed
837
+ - [ ] Component reusability is maximized
838
+ - [ ] Performance considerations are addressed
839
+ - [ ] Accessibility requirements are met
840
+ - [ ] Interactive states documentation is complete
841
+ - [ ] Object syntax standards are enforced
842
+ - [ ] Element syntax standards are enforced
843
+ - [ ] No inline styles prohibition is enforced
844
+ - [ ] Rules are independent and don't share state
845
+ - [ ] Prefer specific outcomes over generic ones
846
+ - [ ] Context is properly defined and not repeated
847
+ - [ ] Each section has appropriate setup and cleanup
848
+
849
+ **Purpose**: The Validation Checklist serves as a quality gate for all styled components, ensuring they meet the established standards for consistency, maintainability, performance, and accessibility. It helps maintain consistency across all UI components and provides a clear framework for developers to follow.