Iconos Lucide React
Receta
Instala Lucide React, importa iconos individuales por nombre y personalízalos con props o clases de Tailwind. Cada ícono es un módulo separado, así que tu bundle solo incluye los iconos que realmente usas.
npm install lucide-react// app/components/icon-demo.tsx
"use client";
import { Search, Menu, X, Settings, Bell } from "lucide-react";
export function IconDemo() {
return (
<div className="flex items-center gap-4">
<Search size={24} color="currentColor" strokeWidth={2} />
<Menu size={24} className="text-gray-600" />
<X size={20} className="text-red-500" />
<Settings size={24} strokeWidth={1.5} />
<Bell size={24} className="text-blue-600" />
</div>
);
}Ejemplo Funcional
Una barra de herramientas de botones de ícono con estados al pasar el cursor y etiquetas de accesibilidad:
// app/components/icon-toolbar.tsx
"use client";
import { Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Link, Image } from "lucide-react";
import { useState } from "react";
interface ToolbarButton {
icon: React.ElementType;
label: string;
action: string;
}
const tools: ToolbarButton[] = [
{ icon: Bold, label: "Bold", action: "bold" },
{ icon: Italic, label: "Italic", action: "italic" },
{ icon: Underline, label: "Underline", action: "underline" },
{ icon: AlignLeft, label: "Align Left", action: "align-left" },
{ icon: AlignCenter, label: "Align Center", action: "align-center" },
{ icon: AlignRight, label: "Align Right", action: "align-right" },
{ icon: Link, label: "Insert Link", action: "link" },
{ icon: Image, label: "Insert Image", action: "image" },
];
export function IconToolbar() {
const [active, setActive] = useState<string | null>(null);
return (
<div className="flex items-center gap-1 rounded-lg border border-gray-200 bg-white p-2">
{tools.map(({ icon: Icon, label, action }) => (
<button
key={action}
onClick={() => setActive(active === action ? null : action)}
aria-label={label}
aria-pressed={active === action}
className={`rounded p-2 transition-colors hover:bg-gray-100 ${
active === action ? "bg-blue-100 text-blue-600" : "text-gray-600"
}`}
>
<Icon size={18} strokeWidth={active === action ? 2.5 : 2} />
</button>
))}
</div>
);
}Análisis Profundo
Cómo Funciona
- Lucide es un fork de Feather Icons con un conjunto de iconos expandido (más de 1,400 iconos) y mantenimiento activo.
- Cada ícono se exporta como una exportación nombrada desde
lucide-react, pero también disponible como módulo independiente enlucide-react/dist/esm/icons/{icon-name}. - Los iconos se renderizan como elementos SVG en línea con valores predeterminados sensatos: tamaño 24x24, ancho de trazo 2px, trazo
currentColor. - El tree-shaking funciona automáticamente con bundlers modernos (webpack 5, Vite, Turbopack) porque cada ícono es una entrada de módulo ES separada.
- Todos los iconos aceptan atributos SVG estándar y atributos React HTML, incluyendo
className,style,onClicky atributos ARIA.
Variaciones
Renderizado dinámico de ícono desde un nombre de cadena:
import { icons } from "lucide-react";
import type { LucideIcon } from "lucide-react";
interface DynamicIconProps {
name: string;
size?: number;
className?: string;
}
export function DynamicIcon({ name, size = 24, className }: DynamicIconProps) {
const IconComponent = icons[name as keyof typeof icons] as LucideIcon | undefined;
if (!IconComponent) {
return null;
}
return <IconComponent size={size} className={className} />;
}
// Uso: <DynamicIcon name="ArrowRight" size={20} className="text-blue-500" />Nota: Importar el objeto icons completo derrota el tree-shaking. Solo usa renderizado dinámico cuando realmente lo necesites (p. ej., selección de ícono impulsada por CMS).
Componente envolvente de ícono personalizado:
import type { LucideIcon } from "lucide-react";
interface IconButtonProps {
icon: LucideIcon;
label: string;
onClick: () => void;
variant?: "default" | "danger" | "success";
}
const variantStyles = {
default: "text-gray-600 hover:bg-gray-100",
danger: "text-red-600 hover:bg-red-50",
success: "text-green-600 hover:bg-green-50",
};
export function IconButton({ icon: Icon, label, onClick, variant = "default" }: IconButtonProps) {
return (
<button
onClick={onClick}
aria-label={label}
className={`rounded-lg p-2 transition-colors ${variantStyles[variant]}`}
>
<Icon size={20} />
</button>
);
}Notas de TypeScript
- Importa el tipo
LucideIconpara tipificar componentes de iconos pasados como props. - Todos los componentes de iconos aceptan
LucideProps, que extiendeSVGProps<SVGSVGElement>consize,color,strokeWidthyabsoluteStrokeWidth. - El objeto
iconsse tipifica comoRecord<string, LucideIcon>, permitiendo búsquedas dinámicas type-safe.
import type { LucideIcon, LucideProps } from "lucide-react";
// Tipifica una prop de ícono
interface Props {
icon: LucideIcon;
iconProps?: LucideProps;
}Inconvenientes
- Importar desde
lucide-reactcon importaciones de barril comoimport * as iconsagrupará cada ícono (más de 1,400 SVGs). Siempre usa importaciones nombradas para compilaciones de producción. - La prop
colorestablece el atributo SVGstroke, nofill. Los iconos Lucide se basan en trazo, así quefillno tiene efecto visible en la mayoría de iconos. - Si usas
classNamepara establecer color vía Tailwind (p. ej.,text-blue-500), no establezca también la propcolor, ya que la prop explícita anulacurrentColor. sizeestablece tantowidthcomoheightsimultáneamente. Para tamaño no cuadrado, usa las propswidthyheightindividualmente.- Las importaciones dinámicas usando
icons[name]no proporcionan validación en tiempo de compilación de que el nombre del ícono existe. Considera construir una lista blanca para escenarios impulsados por CMS.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Lucide React | Tree-shaking excelente, conjunto grande de iconos, mantenimiento activo | El estilo solo de trazo puede no encajar en todos los diseños |
| React Icons | Múltiples familias de iconos en un paquete | Tamaño de instalación más grande, APIs inconsistentes entre familias |
| Heroicons | Proyecto oficial de Tailwind Labs, gran integración de Tailwind | Conjunto de iconos más pequeño (alrededor de 300 iconos) |
| Componentes SVG personalizados | Control total del diseño, cero dependencias | Mantenimiento manual, sin descubrimiento de iconos |
Preguntas Frecuentes
¿Cómo instalas e importas un ícono Lucide en un componente React?
npm install lucide-react
import { Search } from "lucide-react";
export function MyComponent() {
return <Search size={24} className="text-gray-600" />;
}¿Cuáles son el tamaño predeterminado, el ancho de trazo y el color para los iconos Lucide?
- El tamaño predeterminado es de 24x24 píxeles.
- El ancho de trazo predeterminado es de 2px.
- El color predeterminado es
currentColor, que se hereda de la propiedad CSScolordel padre.
¿Por qué Lucide React tiene un buen tree-shaking y cómo puedes romperlo accidentalmente?
- Cada ícono es una entrada de módulo ES separada, así que los bundlers solo incluyen los iconos que importas por nombre.
- Importar el objeto
iconscompleto (import { icons } from "lucide-react") agrupa todos los 1,400+ iconos y derrota el tree-shaking.
¿Cómo renderizas un ícono Lucide dinámicamente desde un nombre de cadena?
import { icons } from "lucide-react";
import type { LucideIcon } from "lucide-react";
const IconComponent = icons[name as keyof typeof icons] as LucideIcon | undefined;
if (IconComponent) return <IconComponent size={24} />;Nota: esto importa todos los iconos y desactiva el tree-shaking.
¿Qué sucede si estableces tanto la prop color como una clase de color de texto de Tailwind en un ícono Lucide?
- La prop
colorexplícita anulacurrentColor. - La clase de Tailwind (p. ej.,
text-blue-500) establececurrentColor, pero la prop tiene prioridad. - Usa una o la otra, no ambas.
¿Cómo creas un componente envolvente de botón de ícono reutilizable con estilos de variante?
import type { LucideIcon } from "lucide-react";
interface IconButtonProps {
icon: LucideIcon;
label: string;
onClick: () => void;
variant?: "default" | "danger";
}
export function IconButton({ icon: Icon, label, onClick, variant = "default" }: IconButtonProps) {
return (
<button onClick={onClick} aria-label={label}>
<Icon size={20} />
</button>
);
}Inconveniente: ¿Por qué establecer fill no tiene efecto visible en la mayoría de los iconos Lucide?
- Los iconos Lucide se basan en trazo, no en relleno.
- La prop
colorcontrola el atributostroke, nofill. - Establecer
fill="red"en un ícono de trazo no cambiará su apariencia.
¿Cómo haces un ícono Lucide no cuadrado (diferente ancho y alto)?
- La prop
sizeestablece tantowidthcomoheightal mismo valor. - Para tamaño no cuadrado, pasa las props
widthyheightindividualmente en lugar desize.
¿Qué tipo de TypeScript deberías usar cuando pasas un ícono Lucide como prop?
import type { LucideIcon, LucideProps } from "lucide-react";
interface Props {
icon: LucideIcon;
iconProps?: LucideProps;
}LucideIcon tipifica el componente en sí; LucideProps extiende SVGProps<SVGSVGElement>.
¿Cómo maneja el ejemplo de la barra de herramientas de iconos la alternancia de estado activo y la accesibilidad?
- Usa
useStatepara rastrear qué herramienta está activa. - Cada botón tiene
aria-labelpara lectores de pantalla yaria-pressedpara indicar el estado de alternancia. - Los iconos activos obtienen un
strokeWidthmás grueso (2.5 vs 2) y un fondo azul.
Inconveniente: ¿Qué riesgo introduce la búsqueda dinámica de iconos desde datos de CMS?
icons[name]no proporciona validación en tiempo de compilación de que el nombre del ícono existe.- Un error tipográfico o un ícono faltante silenciosamente devuelve
undefined. - Construye una lista blanca de nombres de iconos permitidos para escenarios impulsados por CMS.
¿Cuál es el tipo de objeto icons en lucide-react?
- Se tipifica como
Record<string, LucideIcon>. - Esto permite búsquedas dinámicas type-safe pero aún requiere una verificación de existencia en tiempo de ejecución ya que cualquier clave de cadena se acepta.