README · by ansango
← Volver al libro

Estilos personalizados

Accesibilidad en frontend (aria labels, semantic HTML, forms accesibles), consistencia de diseños, temas custom con MUI, responsive design, imágenes y CSS

~4 min de lectura
Resumen

Esta nota cubre las decisiones de estilo y accesibilidad en el frontend: por qué accesibilidad desde el día uno (es legal bajo ADA), cómo hacer forms accesibles con React Hook Form y MUI, mantener consistencia en los diseños, custom themes con MUI, responsive design (mobile-first, breakpoints, imágenes), y las herramientas para verificar todo (Chrome DevTools, Lighthouse, axe-core).

Accesibilidad primero

La accesibilidad puede parecer un afterthought cuando se está sacando un MVP a presión, pero es tan importante como el mobile design. Razones:

Aunque MUI trae muchas features de accesibilidad, tú eres responsable de usarlas:

<button aria-label="Cancel" onClick={onCancel}>Cancel</button>

<img
  src="https://images.unsplash.com/photo-1497531551184-06b252e1bee1"
  alt="Multi-colored hot air balloon with three people in the basket in the sky"
/>

<h1>Welcome to the Test Store</h1>
Semántica > divs

Si tus componentes son todo <div>, replantéate usar elementos semánticos. No afecta al render visual, pero los screen readers y el teclado navegan mucho mejor.

Para forms: instrucciones claras, feedback útil con errores y success messages. El contenido estático debe ser fácil de encontrar para screen readers porque suele tener info importante que no deben perderse.

Form accesible

Forms accesibles son críticos porque permiten a los usuarios tomar acción. Si alguien no puede usar o entender un form, no puede hacer pagos, actualizar info personal, ni solicitar servicios.

Instala React Hook Form:

npm install react-hook-form

Crea src/elements/SearchBar.tsx:

import { Input, InputAdornment, InputLabel } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import { useForm } from 'react-hook-form';
import styled from 'styled-components';

export type SearchBarProps = {
  name: string;
  onSubmitSearch: (searchText: string) => void;
};

const FullWidthForm = styled.form`
  width: 450px;
  @media (max-width: 500px) {
    width: 100%;
  }
`;

const SearchBar = (props: SearchBarProps) => {
  const { onSubmitSearch } = props;
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const searchFieldInputProps = {
    maxLength: 15,
    minLength: 3,
  };

  return (
    <FullWidthForm
      aria-label={`${props.name} search form`}
      onSubmit={handleSubmit(onSubmitSearch)}
    >
      <InputLabel htmlFor="search">Search Input</InputLabel>
      <Input
        placeholder={`Search ${props.name}...`}
        type="search"
        fullWidth
        inputProps={searchFieldInputProps}
        startAdornment={
          <InputAdornment position="start">
            <SearchIcon />
          </InputAdornment>
        }
        {...register('search', { required: true, maxLength: 15, minLength: 3 })}
        aria-invalid={errors.search ? 'true' : 'false'}
      />
      {errors && errors.search && (
        <span>Search text doesn't meet requirements</span>
      )}
    </FullWidthForm>
  );
};

export default SearchBar;

Notas de accesibilidad:

Validación con schemas

Para validaciones más robustas y reusables, usa un schema con Yup, Zod o Joi:

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup
  .object({
    searchText: yup.string().min(3).max(15).required(),
  })
  .required();

const SearchBar = (props: SearchBarProps) => {
  const {
    register, handleSubmit, formState: { errors },
  } = useForm({ resolver: yupResolver(schema) });
  // ...
};
Trabaja con Diseño en mensajes de error

Hay distintas opiniones sobre cuándo mostrar errores (inline al focus, al submit, en bloque). Discute con Diseño qué espera el usuario. Tú aportas qué errores pueden venir del backend.

Verificar accesibilidad

Chrome DevTools

Chrome tiene herramientas para evaluar accesibilidad. Accessibility tab muestra cómo los elementos están agrupados, los labels y la funcionalidad. Puedes ver el orden de tab que sigue el usuario navegando solo con teclado.

Lighthouse

Open source, audita la app en performance, accesibilidad y más. Lighthouse te da una lista concreta de qué mejorar. Útil para demos a Producto y stakeholders.

npm install -g lighthouse
lighthouse http://localhost:5173/ --output-path=./report.json --output json
Local != producción

Lighthouse en local puede dar resultados muy distintos a producción (assets sin comprimir, código sin minificar, configs no optimizados). Para tests representativos, haz un build de producción local y corre Lighthouse sobre ese build.

axe-core

Ligero, se puede añadir al app para testing automatizado durante desarrollo. Encuentra reglas de accesibilidad que se suelen pasar por alto.

import axe from 'axe-core';

axe.run(document, (err, results) => {
  if (err) throw err;
  console.log(results);
});

Configurable para correr solo reglas WCAG específicas o targets concretos. Útil para compliance audits antes de releases.

i18n

La mayoría de apps se usan en más idiomas. Crea archivos por idioma. Las traducciones pueden requerir más espacio, lo que afecta al layout.

Consistencia en diseños

Mantener consistencia con múltiples pantallas, plataformas (Figma, Miro) y diseñadores es difícil. Las inconsistencias se cuelan: cuatro diseños para el mismo modal, paddings diferentes en botones.

Tú tienes que marcar las inconsistencias

Cuando veas una inconsistencia (sutil como un padding, importante como colores en una sección), coméntala. Tu trabajo es mantener la implementación alineada.

Trabaja con el equipo en cómo se implementan estilos:

Documenta las convenciones. Cuando algo cambia, queda en el código como una forma de version control de los diseños. Cualquier dev debería poder explicar dónde está cambiando qué.

Temas custom con MUI

Ya tienes theme.tsx configurado. Actualízalo con cualquier cambio que afecte a MUI components. Ejemplo: customizar un botón para que coincida con la marca.

const theme = createTheme({
  palette: {
    primary: { main: blue[900] },
    secondary: { main: orange[400] },
  },
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          color: '#4C4C4C',
          borderRadius: '4px',
          background: '#B4CD93',
        },
      },
    },
  },
});
Mezcla de estilos globales y locales

Con el tiempo, theme + estilos locales se mezclan. Cuando Diseño pida cambios globales, pregunta: “¿afecta a estilos custom ya hechos?” Esos cambios suelen requerir más trabajo del que Diseño anticipa.

Herramientas de documentación

Responsive design

Mobile-first es lo ideal, pero en apps legacy a veces solo hay desktop y la responsiveness se añade después. Si ese es tu caso, pregunta a Producto y Diseño si debe haber mobile antes de empezar.

Implementación

const StyledHeader = styled.header`
  display: flex;
  justify-content: space-between;
  @media (max-width: 500px) {
    flex-direction: column-reverse;
    width: 100%;
  }
`;

Responsive en componentes desde el inicio

Crea componentes con responsiveness en mente desde el principio. Aunque no tengas los diseños mobile, piensa en cómo se va a comportar en pantallas más pequeñas.

UX considerations

<img
  srcset="tulip-field-320w.jpg, tulip-field-480w.jpg 1.5x, tulip-field-640w.jpg 2x"
  src="tulip-field-640w.jpg"
  alt="A field of tulips blooming"
/>

Mantén aspect ratio y usa SVG para gráficos e ilustraciones (scaling sin pérdida).

Web Components

Custom elements con shadow DOM, mantienen estilos y funcionalidad aislados. Algo a explorar en organizaciones con problemas de style clashing.

Próximos pasos