Cómo crear una sección de features con grid de iconos en React
Una grid de features con iconos en React renderiza una grid CSS responsive de tarjetas, cada una con un icono Lucide resuelto dinámicamente, un título y una descripción corta. Cada tarjeta entra en el viewport con una animación whileInView escalonada mediante Framer Motion, para que la grid se complete de forma natural al hacer scroll.
- Stack: React + Framer Motion + Lucide React, ~170 líneas, cero dependencias adicionales.
- Iconos resueltos en tiempo de ejecución desde el namespace de Lucide, cualquier nombre de icono Lucide funciona como prop string.
- Totalmente compatible con el tema: todos los colores y espaciados usan custom properties CSS (--color-background, --color-accent, etc.).
- Responsive: grid auto-fit con minmax(280px, 1fr) que pasa a una sola columna en pantallas pequeñas.
- Accesible: jerarquía semántica h2/h3, los iconos son decorativos y no requieren aria-label.
Features Icon Grid es una sección React limpia y animada al hacer scroll que presenta hasta seis funcionalidades de producto en una grid centrada de 3 columnas. Cada tarjeta muestra un badge de icono tintado, un título corto y una descripción de una línea. La estética Apple/Stripe, mucho espacio en blanco, texto centrado, color contenido, le permite integrarse en casi cualquier landing SaaS o de agencia sin necesidad de reestilizar.
Anatomía
La sección envuelve dos regiones principales. Arriba, un bloque de cabecera centrado contiene un badge opcional en mayúsculas, un título h2 con tamaño de fuente fluido mediante clamp, y un párrafo de subtítulo; todo el bloque se anima como una sola unidad. Debajo, una grid CSS con `auto-fit, minmax(280px, 1fr)` renderiza las tarjetas de features. Cada tarjeta es una columna flex centrada: un contenedor de icono cuadrado de 48px con un tinte accent al 10%, un h3 y una descripción limitada a 280px de ancho para una longitud de línea cómoda.
Cómo funciona
La animación usa `whileInView` de Framer Motion con `viewport={{ once: true, margin: '-40px' }}` para que cada tarjeta se anime exactamente una vez cuando entra en el área visible. La cabecera aparece con un fundido hacia arriba como una sola unidad (opacity 0 → 1, y 16 → 0). Las tarjetas de features comparten el mismo keyframe pero cada una recibe un retardo de `i * 0.06s`, creando un escalonado de izquierda a derecha en la fila. Todas las transiciones usan la curva cúbica `[0.16, 1, 0.3, 1]`, una curva fast-out-slow-in que resulta ágil sin rebotar. Los componentes de iconos se resuelven en el render: la prop es una cadena como `'Zap'` y el componente hace una búsqueda por clave en el objeto namespace de Lucide.
Cómo crearlo en React
Definir la estructura de datos y las props
Crea una interfaz `FeatureItem` con `id`, `title`, `description` y un campo `icon` string opcional. Pasa un array de estos items al componente junto con un `badge`, `title` y `subtitle` opcionales para la cabecera de sección. Mantén el campo icon como una simple cadena, la resolución ocurre dentro del componente.
interface FeatureItem { id: string; title: string; description: string; icon?: string; }Resolver los iconos Lucide desde una prop string
Importa todo el namespace de Lucide con `* as LucideIcons`, luego escribe un pequeño helper que haga una búsqueda por clave. Castea el namespace a `Record<string, React.ElementType>` para evitar errores de TypeScript. Si el nombre falta o es desconocido, el helper devuelve `null` y el hueco del icono queda vacío.
import * as LucideIcons from "lucide-react"; function getIcon(name?: string) { if (!name) return null; return (LucideIcons as unknown as Record<string, React.ElementType>)[name] || null; }Construir la grid responsive con CSS auto-fit
Usa un simple `div` con `display: grid` y `gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))'`. Esto te da tres columnas en desktop, dos en tablet y una en móvil sin media queries. Ajusta el `gap` a unos 3rem en vertical y 2.5rem en horizontal para ese aspecto SaaS aireado.
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "3rem 2.5rem", maxWidth: 960, margin: "0 auto", }}>Añadir las animaciones whileInView escalonadas
Envuelve cada tarjeta en un `motion.div` con `initial={{ opacity: 0, y: 16 }}` y `whileInView={{ opacity: 1, y: 0 }}`. Ajusta `viewport={{ once: true, margin: '-40px' }}` para que la animación se dispare una vez cuando 40px de la tarjeta son visibles. Multiplica el índice de la tarjeta por 0.06 para el retardo y conseguir un escalonado natural de izquierda a derecha.
<motion.div key={feature.id} initial={{ opacity: 0, y: 16 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true, margin: "-40px" }} transition={{ delay: i * 0.06, duration: 0.5, ease: [0.16, 1, 0.3, 1] }} >
Cuándo usarlo
Coloca esta sección justo después del hero en una landing SaaS o de producto, funciona mejor como primera explicación de lo que hace tu producto. Seis features es el punto ideal; por debajo de cuatro la grid se ve escasa, y por encima de ocho conviene plantear otro layout como un split alterno. Evítala en páginas con mucha carga cognitiva como la documentación o las tablas de precios, donde la carga ya es alta. En móvil la grid auto-fit se repliega con elegancia a una sola columna, así que no hace falta trabajo adicional.
Usado por
- Stripe, Usa una grid de iconos centrada en sus páginas de producto para presentar las capacidades de su API con contenedores de iconos tintados y descripciones cortas y contundentes.
- Linear, Presenta las funcionalidades de workflow en una grid multicolumna aireada con badges de iconos y prosa mínima, la misma filosofía de espaciado.
- Vercel, Las grids de features con contenedores de iconos monocromos son un patrón recurrente en sus páginas de plataforma e infraestructura.
- Notion, Despliega una grid de icono y texto centrada en sus páginas para equipos para desglosar las funcionalidades de colaboración en un formato fácil de escanear.
FAQ
¿Cómo añado o cambio iconos sin tocar el componente?
Pasa el nombre de icono Lucide en PascalCase exacto como cadena `icon` en tus datos, por ejemplo `'Zap'`, `'Shield'` o `'BarChart2'`. El componente lo resuelve en tiempo de ejecución desde el namespace de Lucide, así que cualquier nombre de icono Lucide válido funciona sin modificar el código del componente.
¿Puedo usar un número de features distinto de seis?
La grid auto-fit se adapta a cualquier cantidad. Cuatro items dan un layout 2×2 en desktop, cinco dan una fila desigual. Para el resultado más equilibrado, quédate con múltiplos de tres (3, 6, 9). Si necesitas cinco o siete features, define un `gridTemplateColumns` explícito como `repeat(3, 1fr)` para que la última fila se alinee a la izquierda en lugar de estirarse.
¿Cómo desactivo las animaciones al hacer scroll?
Reemplaza los `motion.div` por simples `div` y elimina las props `initial`, `whileInView` y `transition`. La grid y el layout quedan intactos, Framer Motion solo se usa para la animación de entrada, no para el posicionamiento.
¿Esta sección admite el modo oscuro y claro?
Sí. Cada valor de color lee una custom property CSS (--color-background, --color-foreground, --color-accent, --color-foreground-muted). Basta con cambiar el tema a nivel del atributo `data-theme`; no hace falta ningún cambio dentro del componente.