Renders an SVG icon with optimal performance.
<P> This icon has the same size as this font: <Icon name="avatar" size="font" label="Account" className="text-emerald-500" /></P><Icon name="avatar" size="sm" label="Account" className="text-emerald-500" /><Icon name="avatar" size="md" label="Account" className="text-emerald-500" /><Icon name="avatar" size="lg" label="Account" className="text-emerald-500" />
Here's the <Icon />
component in action.
This icon has the same size as this font: Account
AccountAccountAccountSVG sprites are useful if you want to style them via CSS, like changing the foreground color with text-emerald-500
for example. If you want to load complex SVGs, use standard images instead with a simple <img src>
.
Here's the workflow for adding new SVG sprites to the <Icon>
component:
./icon/svgs
folder that comes with this componentnpm run build:icons
to update the SVG spriteInstall the icon component
Download and unzip the file into the /app/components/media
folder
<VisuallyHidden>
componentInstall the tailwind-merge.ts
file
import { type ClassValue, clsx } from 'clsx'import { extendTailwindMerge } from 'tailwind-merge'
const twMerge = extendTailwindMerge<string, string>({ extend: { classGroups: { colors: [{ 'text-muted': ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] }, 'text-ring'], spacing: [{ 'text-fluid': ['6xl', '5xl', '4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs', 'tooltip'] }], }, },})
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs))
Install Prettier (if you haven't).
Run npm i -D prettier
built-icons.js
uses it to format the generated files.
Add this to your package.json
scripts
"build:icons": "node app/components/media/icon/build-icons.js"
Check if everything is working
Runnpm run build:icons
Finally make sure to preload your sprite.svg
file:
<head> <link rel="preload" as="image/svg+xml" href="path/to/sprite.svg"></head>
Depending on your server’s configuration, you might need to make sure the proper cache-headers are set on your sprite.svg
so the browser can cache it appropriately.
import { type SVGProps } from 'react'import { cn } from '#app/utils/tailwind-merge.ts'import { type IconName } from './icon/name.js'import href from './icon/sprite.svg'
export type IconProps = SVGProps<SVGSVGElement> & { name: IconName; label?: string; size?: 'font' | 'sm' | 'md' | 'lg'; children?: never }
/** * Renders an SVG icon with optimal performance. */const Icon = ({ name, label, size = 'font', className, ...props }: IconProps) => { return ( <> <svg aria-hidden="true" focusable="false" // See: https://allyjs.io/tutorials/focusing-in-svg.html#making-svg-elements-focusable {...props} className={cn('icon', size, className)} > <use href={`${href}#${name}`} /> </svg> {label ? <span className="sr-only">{label}</span> : null} </> )}export { Icon, IconName, href }
.icon { @apply inline;
/* Sizes */ &.font { @apply h-[1em] w-[1em]; } /* Sizes */ &.sm { @apply h-fluid-12 w-fluid-12; } &.md { @apply h-fluid-14 w-fluid-14; } &.lg { @apply h-fluid-15 w-fluid-15; }}