> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/styling-and-theming.md).

# Estilización y tematización

Esta página cubre estándares completos de estilos para las UIs web de Decentraland usando styled-components con la solución de estilos de Material UI.

{% hint style="info" %}
Todos los ejemplos de código en este documento son ilustrativos. No representan componentes de producción, sino que demuestran patrones y estándares.
{% endhint %}

## Principios fundamentales

1. **Solo sintaxis de objeto** - Usar notación de objeto para un fuerte soporte de TypeScript
2. **Tema primero** - Todos los valores provienen del tema UI2
3. **No estilos en línea** - Usar componentes styled para todo el estilado
4. **Todo tipado** - Aprovechar TypeScript para props y tema
5. **Responsivo por defecto** - Usar los breakpoints del tema

***

## Estándar de sintaxis de objeto

Componentes UI2 **DEBEN** usar la sintaxis de objeto. Esto garantiza un fuerte soporte de TypeScript, validación de csstype e integración directa con el tema.

{% hint style="warning" %}
La sintaxis de template literal **no está permitida**. Siempre usar notación de objeto.
{% endhint %}

### Ejemplo básico

```tsx
// ✅ Correcto: Sintaxis de objeto
const Button = styled('button')({
  color: 'turquoise',
  padding: '8px 16px',
});

// ❌ Incorrecto: Sintaxis de template literal
const Button = styled.button`
  color: turquoise;
  padding: 8px 16px;
`;
```

### Con tema y props

```tsx
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
}

// ✅ Correcto: Sintaxis de objeto con theme y props
const Button = styled('button')<ButtonProps>(({ theme, primary, size = 'medium' }) => ({
  color: primary ? theme.palette.primary.main : theme.palette.text.primary,
  backgroundColor: primary ? theme.palette.primary.main : 'transparent',
  borderRadius: theme.shape.borderRadius,
  padding: {
    small: theme.spacing(0.5, 1),
    medium: theme.spacing(1, 2),
    large: theme.spacing(1.5, 3),
  }[size],
}));
```

{% hint style="danger" %}
**Regla**: Los valores DEBEN siempre provenir del tema UI2. No se permiten códigos hexadecimales arbitrarios ni valores en píxeles.
{% endhint %}

***

## Sintaxis de elemento

Al estilizar elementos HTML nativos, siempre usar la forma de llamada de función `styled('tag')`.

### Sintaxis correcta

```tsx
// ✅ Correcto: Forma de llamada de función
const Container = styled('div')({
  display: 'flex',
  flexDirection: 'column',
});

const Action = styled('button')(({ theme }) => ({
  color: theme.palette.primary.main,
  padding: theme.spacing(1, 2),
}));

const Label = styled('label')(({ theme }) => ({
  color: theme.palette.text.secondary,
  fontSize: theme.typography.caption.fontSize,
}));
```

### Sintaxis incorrecta

```tsx
// ❌ Incorrecto: Forma de propiedad (sintaxis heredada)
const Container = styled.div`
  display: flex;
  flex-direction: column;
`;

const Action = styled.button`
  color: ${props => props.theme.palette.primary.main};
`;
```

***

## No estilos en línea

Los estilos en línea (`style={...}`) **NO DEBEN** usarse en componentes UI2.

### ¿Por qué no estilos en línea?

* Evitan el tipado del tema
* Más difícil de mantener
* Previenen la reutilización
* No pueden ser optimizados
* Sin validación de TypeScript

### La forma correcta

```tsx
// ❌ Incorrecto: Estilos en línea
<Card 
  key={id} 
  style={{ backgroundColor: color } as React.CSSProperties}
>
  <CardHeader>
    <Title style={{ fontSize: 20 }}>{title}</Title>
  </CardHeader>
</Card>

// ✅ Correcto: Componentes styled con props
interface CardProps {
  backgroundColor: string;
}

interface TitleProps {
  size: number;
}

const StyledCard = styled('div')<CardProps>(({ backgroundColor, theme }) => ({
  backgroundColor,
  borderRadius: theme.shape.borderRadius,
  padding: theme.spacing(2),
}));

const Title = styled('h2')<TitleProps>(({ size, theme }) => ({
  fontSize: theme.typography.pxToRem(size),
  color: theme.palette.text.primary,
}));

<StyledCard key={id} backgroundColor={color}>
  <CardHeader>
    <Title size={20}>{title}</Title>
  </CardHeader>
</StyledCard>
```

***

## Breakpoints

Usa `theme.breakpoints` helpers en lugar de valores de píxel codificados.

### Helpers de breakpoint

| Helper                | Uso                                     | Descripción                 |
| --------------------- | --------------------------------------- | --------------------------- |
| `up(key)`             | `theme.breakpoints.up('md')`            | Ancho mínimo y hacia arriba |
| `down(key)`           | `theme.breakpoints.down('md')`          | Ancho máximo y hacia abajo  |
| `between(start, end)` | `theme.breakpoints.between('sm', 'lg')` | Entre dos breakpoints       |
| `only(key)`           | `theme.breakpoints.only('md')`          | Solo en este breakpoint     |

### Ejemplos

```tsx
// ❌ Incorrecto: Breakpoints codificados
const Panel = styled('div')`
  @media (max-width: 768px) {
    width: 100%;
  }
`;

// ✅ Correcto: Breakpoints del tema
interface PanelProps {
  expanded: boolean;
}

const Panel = styled('div')<PanelProps>(({ theme, expanded }) => ({
  width: expanded ? '400px' : '0',
  transition: theme.transitions.create('width'),
  
  [theme.breakpoints.down('sm')]: {
    width: expanded ? '100%' : '0',
  },
}));
```

### Múltiples breakpoints

```tsx
const Layout = styled('div')(({ theme }) => ({
  display: 'grid',
  gridTemplateColumns: '1fr 320px',
  gap: theme.spacing(2),
  
  // Móvil: una sola columna
  [theme.breakpoints.down('md')]: {
    gridTemplateColumns: '1fr',
  },
  
  // Escritorio grande: barra lateral más ancha
  [theme.breakpoints.up('xl')]: {
    gridTemplateColumns: '1fr 400px',
  },
  
  // Rango de tablet: gap diferente
  [theme.breakpoints.between('sm', 'lg')]: {
    gap: theme.spacing(3),
  },
  
  // Solo tablet
  [theme.breakpoints.only('md')]: {
    padding: theme.spacing(2),
  },
}));
```

***

## Escala de espaciado

Usa `theme.spacing` exclusivamente para márgenes, paddings y gaps.

### Convención de espaciado

* `theme.spacing(n)` donde `n` es un número
* La unidad base es típicamente 8px
* `spacing(1)` = 8px, `spacing(2)` = 16px, etc.
* Se permiten decimales: `spacing(1.5)` = 12px

```tsx
// ✅ Correcto: Espaciado del tema
const Box = styled('div')(({ theme }) => ({
  padding: theme.spacing(2),           // 16px
  margin: theme.spacing(1, 0),         // 8px vertical, 0 horizontal
  gap: theme.spacing(1.5),             // 12px
  paddingInline: theme.spacing(3),     // 24px izquierda/derecha
}));

// ❌ Incorrecto: Valores en píxeles sin procesar
const Box = styled('div')({
  padding: '16px',
  margin: '8px 0',
  gap: '12px',
});
```

### Patrones comunes de espaciado

```tsx
const Card = styled('div')(({ theme }) => ({
  // Espaciado uniforme por todos lados
  padding: theme.spacing(3),
  
  // Vertical/horizontal diferente
  padding: theme.spacing(2, 3),  // 16px vertical, 24px horizontal
  
  // Todos los lados diferentes
  padding: theme.spacing(1, 2, 3, 2),  // arriba, derecha, abajo, izquierda
  
  // Propiedades lógicas (preferidas para soporte RTL)
  paddingBlock: theme.spacing(2),      // arriba y abajo
  paddingInline: theme.spacing(3),     // izquierda y derecha
  marginBlockStart: theme.spacing(1),  // margin-top
}));
```

***

## Z-Index y apilamiento

Seguir la escala de z-index del tema. Nunca usar valores de z-index arbitrarios.

### Valores de z-index del tema

```tsx
theme.zIndex.mobileStepper  // 1000
theme.zIndex.fab            // 1050
theme.zIndex.speedDial      // 1050
theme.zIndex.appBar         // 1100
theme.zIndex.drawer         // 1200
theme.zIndex.modal          // 1300
theme.zIndex.snackbar       // 1400
theme.zIndex.tooltip        // 1500
```

### Uso correcto

```tsx
// ✅ Correcto: z-index del tema
const StickyBar = styled('div')(({ theme }) => ({
  position: 'sticky',
  top: 0,
  zIndex: theme.zIndex.appBar,
  backgroundColor: theme.palette.background.paper,
}));

const Overlay = styled('div')(({ theme }) => ({
  position: 'fixed',
  inset: 0,
  zIndex: theme.zIndex.modal,
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
}));

// ❌ Incorrecto: z-index arbitrario
const StickyBar = styled('div')({
  position: 'sticky',
  top: 0,
  zIndex: 999,  // No hacer esto
});
```

### Buenas prácticas de contexto de apilamiento

* Ser consciente de crear nuevos contextos de apilamiento
* Evitar `position: relative` innecesario en los padres
* Documentar por qué se necesita un z-index
* Si necesitas una nueva capa, agrégala primero al tema

***

## Tokens de color

Siempre consumir colores desde `theme.palette` y `dclColors`. No valores hex ad-hoc.

### Estructura de la paleta

```tsx
// Colores de texto
theme.palette.text.primary
theme.palette.text.secondary
theme.palette.text.disabled

// Colores de fondo
theme.palette.background.default
theme.palette.background.paper

// Primary/Secondary/Error
theme.palette.primary.main
theme.palette.primary.light
theme.palette.primary.dark
theme.palette.primary.contrastText

// Colores de acción
theme.palette.action.active
theme.palette.action.hover
theme.palette.action.selected
theme.palette.action.disabled
theme.palette.action.disabledBackground

// Dividers
theme.palette.divider
```

### Colores de Decentraland

```tsx
import { dclColors } from 'decentraland-ui2';

// Colores de rareza
dclColors.rarity.unique
dclColors.rarity.mythic
dclColors.rarity.legendary
dclColors.rarity.epic
dclColors.rarity.rare
dclColors.rarity.uncommon
dclColors.rarity.common
```

### Ejemplos

```tsx
// ✅ Correcto: Colores del tema
const Chip = styled('span')(({ theme }) => ({
  color: theme.palette.text.secondary,
  backgroundColor: theme.palette.background.paper,
  borderColor: theme.palette.divider,
  
  '&:hover': {
    backgroundColor: theme.palette.action.hover,
  },
}));

const RarityBadge = styled('span')<{ rarity: string }>(({ rarity }) => ({
  backgroundColor: dclColors.rarity[rarity],
  color: '#FFFFFF', // Color de contraste - aceptable si está documentado
  padding: '4px 8px',
  borderRadius: '4px',
}));

// ❌ Incorrecto: Valores hexadecimales arbitrarios
const Chip = styled('span')({
  color: '#666666',
  backgroundColor: '#FFFFFF',
  borderColor: '#E0E0E0',
});
```

***

## Estados interactivos

Todos los controles interactivos DEBEN mostrar estados visibles para hover, focus, active y disabled.

### Componente interactivo completo

```tsx
const InteractiveButton = styled('button')(({ theme }) => ({
  // Estado base
  padding: theme.spacing(1, 2),
  backgroundColor: theme.palette.primary.main,
  color: theme.palette.primary.contrastText,
  border: 'none',
  borderRadius: theme.shape.borderRadius,
  cursor: 'pointer',
  outline: 'none',
  transition: theme.transitions.create([
    'background-color',
    'transform',
    'box-shadow',
  ]),
  
  // Estado hover (mouse)
  '&:hover': {
    backgroundColor: theme.palette.primary.dark,
  },
  
  // Estado focus (navegación por teclado)
  '&:focus-visible': {
    outline: `2px solid ${theme.palette.primary.main}`,
    outlineOffset: 2,
    boxShadow: theme.shadows[2],
  },
  
  // Estado active/pressed
  '&:active': {
    backgroundColor: theme.palette.primary.dark,
    transform: 'scale(0.98)',
  },
  
  // Estado disabled
  '&:disabled': {
    backgroundColor: theme.palette.action.disabledBackground,
    color: theme.palette.action.disabled,
    cursor: 'not-allowed',
    transform: 'none',
  },
}));
```

### Patrón Focus-Visible

Siempre usar `:focus-visible` en lugar de `:focus` para evitar mostrar anillos de foco en clics con el mouse:

```tsx
// ✅ Correcto: Solo muestra el anillo de foco para teclado
'&:focus-visible': {
  outline: `2px solid ${theme.palette.primary.main}`,
  outlineOffset: 2,
}

// ❌ Incorrecto: Muestra el anillo de foco en cada clic
'&:focus': {
  outline: `2px solid ${theme.palette.primary.main}`,
}
```

***

## Tipografía

Usar las variantes tipográficas del tema en lugar de propiedades de fuente personalizadas.

```tsx
// ✅ Correcto: Variantes tipográficas
const Heading = styled('h1')(({ theme }) => ({
  ...theme.typography.h1,
  marginBottom: theme.spacing(2),
}));

const Body = styled('p')(({ theme}) => ({
  ...theme.typography.body1,
  color: theme.palette.text.secondary,
}));

// Con el componente MUI Typography (preferido)
<Typography variant="h1">Heading</Typography>
<Typography variant="body1">Body text</Typography>

// ❌ Incorrecto: Tipografía personalizada
const Heading = styled('h1')({
  fontSize: '32px',
  fontWeight: 700,
  lineHeight: 1.2,
});
```

***

## Optimización de rendimiento

### No crear componentes styled en el render

```tsx
// ❌ Incorrecto: Crea un nuevo componente en cada render
function Component({ color }) {
  const Box = styled('div')({
    backgroundColor: color,
  });
  return <Box />;
}

// ✅ Correcto: Crear una vez, pasar props
const Box = styled('div')<{ color: string }>(({ color }) => ({
  backgroundColor: color,
}));

function Component({ color }) {
  return <Box color={color} />;
}
```

### Memoizar props derivadas

```tsx
// ❌ Incorrecto: Crea un nuevo objeto en cada render
<StyledComponent style={{ color: isDark ? 'white' : 'black' }} />

// ✅ Correcto: Pasar props primitivos
const StyledComponent = styled('div')<{ isDark: boolean }>(({ isDark, theme }) => ({
  color: isDark ? theme.palette.common.white : theme.palette.common.black,
}));

<StyledComponent isDark={isDark} />
```

***

## Convenciones de nombres

### Nombres de componentes

* **PascalCase** para componentes
* Nombres descriptivos

```tsx
// ✅ Buenos nombres
const UserCard = styled('div')({...});
const PrimaryButton = styled('button')({...});
const NavigationList = styled('ul')({...});

// ❌ Malos nombres
const card = styled('div')({...});
const btn = styled('button')({...});
const list1 = styled('ul')({...});
```

### Estructura de archivos

```
Component.tsx        # Componente principal
Component.styles.ts  # Componentes styled
Component.test.tsx   # Tests
Component.stories.tsx # Storybook
```

***

## Siguientes pasos

* Revisar [Componentes personalizados](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/custom-components.md) para pautas de creación de componentes
* Ver [Guía de migración](https://github.com/decentraland/docs/blob/main/contributor/contributor-guides/web-ui-standards/broken-reference/README.md) para migraciones de UI1 a UI2
* Comprobar [Resumen del proceso](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/process-overview.md) para el flujo de trabajo completo


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/styling-and-theming.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
