Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Crie componentes de ícones SVG reutilizáveis e acessíveis em React. Use SVG inline para controle total, SVGR para importar arquivos .svg como componentes, ou um sprite SVG para desempenho otimizado com muitos ícones.
SVG inline como um 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>
);
}Uma biblioteca de componentes de conjunto de ícones personalizados com API e acessibilidade consistentes:
// 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 ícones
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">
Tamanho:
<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">
Cor:
<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 faz com que os ícones SVG herdem a propriedade CSS color do elemento pai, permitindo a estilização com classes de cor de texto do Tailwind.viewBox="0 0 24 24" define o espaço de coordenadas. Os atributos width e height controlam o tamanho renderizado independentemente.createIcon mantém a API consistente entre todos os ícones, evitando a repetição de boilerplate.aria-hidden="true") ou informativos (role="img" com aria-label).SVGR para importar arquivos .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;// Agora importe SVGs como componentes
import Logo from "@/public/icons/logo.svg";
export function Header() {
return <Logo className="h-8 w-8 text-blue-600" />;
}Abordagem de sprite SVG para muitos ícones:
// 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 os atributos SVG nativos em seus componentes de ícone.Omit para remover children das props do SVG, já que o conteúdo do ícone é fixo..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) em vez de kebab-case. Caso contrário, o React avisará sobre propriedades inválidas do DOM.viewBox ao otimizar SVGs com SVGO. Removê-lo quebra o comportamento de dimensionamento.fill e stroke têm o valor padrão black em SVG. Defina fill="none" para ícones baseados em traço ou stroke="none" para ícones preenchidos explicitamente.title por padrão, que cria uma tooltip ao passar o mouse. Desative-o com titleProp: false na configuração do SVGR, se indesejado.use href não suportam currentColor do CSS para sprites de origem cruzada. O sprite deve estar no mesmo domínio.| Abordagem | Prós | Contras |
|---|---|---|
| Componentes SVG inline | Controle total, tree-shakeable, estilização CSS | Verboso, aumenta o bundle por ícone |
| Importações SVGR | Use arquivos .svg diretamente, otimização automática | Requer configuração webpack, etapa de build |
| Sprites SVG | Requisição única para todos os ícones, cache | Sem tree-shaking, gerenciamento manual de sprites |
| Fontes de ícones | Bundle minúsculo, uso familiar de CSS | Borrado em tamanhos pequenos, estilo limitado |
| Bibliotecas de ícones (Lucide) | Pronto, consistente, bem mantido | Dependência externa, menos controle |
.svg como componentes React através de um loader webpack.<use href>.stroke="currentColor" faz com que o ícone herde a propriedade CSS color do pai.className="text-blue-500" no pai ou no ícone, e a cor do traço é atualizada automaticamente.size, label e className.displayName para o React DevTools.aria-hidden="true" e omita role.role="img" e forneça aria-label com uma descrição.label é fornecida para decidir.viewBox quebra completamente o comportamento de dimensionamento.width/height.{ name: "removeViewBox", active: false }.// next.config.ts
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: [{ loader: "@svgr/webpack" }],
});
return config;
}Então: 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> em um único arquivo SVG.<use href="/icons/sprite.svg#icon-name">.currentColor não funciona para sprites de origem cruzada; o sprite deve estar no mesmo domínio.black.fill="none".stroke="none".strokeWidth em vez de stroke-width, viewBox em vez de viewbox.export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "children"> {
size?: number;
label?: string;
}Use Omit para remover children, pois o conteúdo do ícone é fixo.
Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥