Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Crea componentes de ícono SVG reutilizables y accesibles en React. Usa SVG en línea para tener control total, SVGR para importar archivos .svg como componentes, o un sprite SVG para rendimiento óptimo con muchos iconos.
SVG en línea como componente:
// components/icons/check-icon.tsx
interface IconProps {
size?: number;
color?: string;
className?: string;
"aria-label"?: string;
}
export function CheckIcon({ size = 24, color = "currentColor", className, ...props }: IconProps) {
const isDecorative = !props["aria-label"];
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
role={isDecorative ? undefined : "img"}
aria-hidden={isDecorative ? true : undefined}
aria-label={props["aria-label"]}
>
<polyline points="20 6 9 17 4 12" />
</svg>
);
}Una biblioteca de componentes de conjunto de iconos personalizados con API consistente y accesibilidad:
// components/icons/icon.tsx
import { type SVGProps } from "react";
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "children"> {
size?: number;
label?: string;
}
function createIcon(path: React.ReactNode, displayName: string) {
function Icon({ size = 24, label, className, ...props }: IconProps) {
const isDecorative = !label;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
role={isDecorative ? undefined : "img"}
aria-hidden={isDecorative ? true : undefined}
aria-label={label}
{...props}
>
{path}
</svg>
);
}
Icon.displayName = displayName;
return Icon;
}
// Define iconos
export const ArrowRight = createIcon(
<line x1="5" y1="12" x2="19" y2="12"><polyline points="12 5 19 12 12 19" /></line>,
"ArrowRight"
);
export const Close = createIcon(
<><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>,
"Close"
);
export const Heart = createIcon(
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />,
"Heart"
);
export const Star = createIcon(
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />,
"Star"
);
export const Search = createIcon(
<><circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></>,
"Search"
);
export const Menu = createIcon(
<><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></>,
"Menu"
);// app/components/icon-showcase.tsx
"use client";
import { useState } from "react";
import { ArrowRight, Close, Heart, Star, Search, Menu } from "@/components/icons/icon";
import type { IconProps } from "@/components/icons/icon";
const icons = [
{ component: ArrowRight, name: "ArrowRight" },
{ component: Close, name: "Close" },
{ component: Heart, name: "Heart" },
{ component: Star, name: "Star" },
{ component: Search, name: "Search" },
{ component: Menu, name: "Menu" },
];
export function IconShowcase() {
const [iconSize, setIconSize] = useState(24);
const [iconColor, setIconColor] = useState("#374151");
return (
<div className="space-y-6">
<div className="flex items-center gap-6">
<label className="flex items-center gap-2 text-sm">
Tamaño:
<input
type="range"
min={16}
max={48}
value={iconSize}
onChange={(e) => setIconSize(Number(e.target.value))}
className="w-32"
/>
<span className="w-8 text-right">{iconSize}</span>
</label>
<label className="flex items-center gap-2 text-sm">
Color:
<input
type="color"
value={iconColor}
onChange={(e) => setIconColor(e.target.value)}
className="h-8 w-8 cursor-pointer"
/>
</label>
</div>
<div className="grid grid-cols-3 gap-4 sm:grid-cols-6">
{icons.map(({ component: Icon, name }) => (
<div
key={name}
className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4"
>
<Icon size={iconSize} color={iconColor} label={name} />
<span className="text-xs text-gray-500">{name}</span>
</div>
))}
</div>
</div>
);
}currentColor hace que los iconos SVG hereden la propiedad CSS color del elemento padre, permitiendo estilos con clases de color de texto de Tailwind.viewBox="0 0 24 24" define el espacio de coordenadas. Los atributos width y height controlan el tamaño renderizado independientemente.createIcon mantiene la API consistente en todos los iconos mientras evita código repetitivo.aria-hidden="true") o informativos (role="img" con aria-label).SVGR para importar archivos .svg como componentes React:
npm install @svgr/webpack// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ name: "removeViewBox", active: false }],
},
},
},
],
});
return config;
},
};
export default nextConfig;// Ahora importa SVGs como componentes
import Logo from "@/public/icons/logo.svg";
export function Header() {
return <Logo className="h-8 w-8 text-blue-600" />;
}Enfoque de sprite SVG para muchos iconos:
// public/icons/sprite.svg
// <svg xmlns="http://www.w3.org/2000/svg">
// <symbol id="icon-check" viewBox="0 0 24 24">
// <polyline points="20 6 9 17 4 12" />
// </symbol>
// <symbol id="icon-close" viewBox="0 0 24 24">
// <line x1="18" y1="6" x2="6" y2="18" />
// <line x1="6" y1="6" x2="18" y2="18" />
// </symbol>
// </svg>
interface SpriteIconProps {
name: string;
size?: number;
className?: string;
label?: string;
}
export function SpriteIcon({ name, size = 24, className, label }: SpriteIconProps) {
return (
<svg
width={size}
height={size}
className={className}
role={label ? "img" : undefined}
aria-hidden={label ? undefined : true}
aria-label={label}
>
<use href={`/icons/sprite.svg#icon-${name}`} />
</svg>
);
}
// Uso: <SpriteIcon name="check" size={20} className="text-green-500" />SVGProps<SVGSVGElement> para permitir todos los atributos SVG nativos en tus componentes de iconos.Omit para eliminar children de los props SVG ya que el contenido del icono es fijo..svg.// types/svg.d.ts
declare module "*.svg" {
import type { FC, SVGProps } from "react";
const content: FC<SVGProps<SVGSVGElement>>;
export default content;
}strokeWidth, viewBox, fillRule) en lugar de kebab-case. React advertirá sobre propiedades DOM inválidas de lo contrario.viewBox cuando optimices SVGs con SVGO. Eliminarlo rompe el comportamiento de escalado.fill y stroke por defecto son black en SVG. Establece fill="none" para iconos basados en stroke o stroke="none" para iconos rellenos explícitamente.title por defecto, lo que crea un tooltip al pasar el ratón. Desactívalo con titleProp: false en la configuración de SVGR si no lo deseas.use href no soportan CSS currentColor para sprites de diferente origen. El sprite debe estar en el mismo dominio.| Enfoque | Pros | Contras |
|---|---|---|
| Componentes SVG en línea | Control total, tree-shakeable, estilos CSS | Verboso, aumenta el bundle por icono |
| Importaciones SVGR | Usa archivos .svg directamente, auto-optimización | Requiere configuración webpack, paso de compilación |
| Sprites SVG | Una sola solicitud para todos los iconos, en caché | Sin tree-shaking, gestión manual del sprite |
| Fuentes de iconos | Bundle diminuto, uso CSS familiar | Borroso en tamaños pequeños, estilos limitados |
| Bibliotecas de iconos (Lucide) | Listos para usar, consistentes, bien mantenidos | Dependencia externa, menos control |
.svg como componentes React vía un loader webpack.<use href>.stroke="currentColor" hace que el icono herede la propiedad CSS color del padre.className="text-blue-500" en el padre o en el icono, y el color del stroke se actualiza automáticamente.size, label y className.displayName para React DevTools.aria-hidden="true" y omite role.role="img" y proporciona aria-label con una descripción.label para decidir.viewBox rompe el comportamiento de escalado por completo.width/height.{ name: "removeViewBox", active: false }.// next.config.ts
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: [{ loader: "@svgr/webpack" }],
});
return config;
}Luego: import Logo from "@/public/icons/logo.svg";
// types/svg.d.ts
declare module "*.svg" {
import type { FC, SVGProps } from "react";
const content: FC<SVGProps<SVGSVGElement>>;
export default content;
}<symbol> en un único archivo SVG.<use href="/icons/sprite.svg#icon-name">.currentColor no funciona para sprites de diferente origen; el sprite debe estar en el mismo dominio.black.fill="none".stroke="none".strokeWidth no stroke-width, viewBox no viewbox.export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "children"> {
size?: number;
label?: string;
}Usa Omit para eliminar children ya que el contenido del icono es fijo.
Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥