Blog / Development

How to add SVG illustrations to React and Next.js

There are four common ways to put an SVG illustration into a React or Next.js app. They differ in one important way: whether your CSS can reach inside the drawing. Here is each one with code, and when to use it.

By Abdullah NomanOctober 13, 20263 min read

Desktop monitor with code, hairline isometric illustration

SVG is the right format for product illustrations: it is small, sharp at every size, and editable with code. But "use an SVG" can mean four different things in React, and the choice decides whether you can change colours with CSS, support dark mode, or animate the lines.

Option 1: an img tag

The simplest way. Put the file in public/ and reference it.

// app/inbox/empty.tsx
export function EmptyInbox() {
  return <img src="/illustrations/inbox-zero-tray.svg" alt="" width={200} height={200} />
}

In Next.js you can use next/image the same way. SVGs are not resized by the image optimiser, so a plain <img> is fine.

Good: cached by the browser, keeps the SVG out of your JavaScript bundle, works with any file. Limit: the SVG is a sealed box. Your page's CSS cannot change its colours or animate its paths, so for dark mode you need a second file.

Option 2: inline SVG

Paste the SVG markup into the component. React needs a few attribute changes (class to className, stroke-width to strokeWidth), which converters do for you.

export function EmptyInbox() {
  return (
    <svg viewBox="0 0 400 400" aria-hidden="true" className="h-48 w-48">
      <path d="M80 220 L200 150 L320 220 L200 290 Z"
            fill="none" stroke="currentColor" strokeWidth={1.2} />
      {/* ...more paths */}
    </svg>
  )
}

Good: CSS reaches every path, so currentColor, CSS variables, dark mode and animation all work. Limit: the markup ships in your JavaScript, which matters if you have dozens of large illustrations on one page.

Option 3: SVGR (import an .svg as a component)

SVGR turns .svg files into React components at build time, so you keep files as files and still get inline SVG. In a Vite project, use the vite-plugin-svgr plugin and import with ?react:

import InboxTray from "./inbox-zero-tray.svg?react"

export function EmptyInbox() {
  return <InboxTray aria-hidden="true" className="h-48 w-48 text-indigo-600" />
}

In Next.js, add @svgr/webpack to the webpack config (or the equivalent Turbopack rule) and import .svg files directly. Check the current Next.js docs for your version, since the config moved between releases.

If you would rather not add a build step, import the raw markup as a string (import svg from "./file.svg?raw" in Vite) and render it with a small wrapper component.

Option 4: a shadcn registry

If you use shadcn/ui, illustration libraries can publish a registry so components install like any shadcn component. With Liniqo, add the registry to components.json:

{
  "registries": {
    "@liniqo": "https://liniqo.com/r/{name}.json"
  }
}

Then install an illustration by its id and use it with props:

npx shadcn@latest add @liniqo/car-hero-price

import { LiniqoCarHeroPrice } from "@/components/liniqo/illustrations/car-hero-price"

<LiniqoCarHeroPrice accent="#e5484d" theme="auto" animation="draw" size={320} />

The component is inline SVG under the hood, so colours, theme and animation are props. The full prop list is in the React docs.

Terminal window with prompt, hairline isometric illustration
Install illustrations from the terminal like any other component. Terminal window with prompt

Which one should you use?

NeedUse
A few static images, no themingimg tag
Dark mode, brand colours or animationInline SVG, SVGR or a registry component
Many illustrations on one long pageimg tags with loading="lazy", or inline only the ones above the fold
shadcn/ui project, want propsRegistry component

Theming: let CSS variables do the work

A well-built illustration SVG does not hard-code its colours. It references CSS variables with a fallback, like stroke="var(--hl-ink, #15171c)". Inline, those variables can be set by your theme:

:root      { --hl-ink: #15171c; --hl-accent: #3a57ff; }
.dark      { --hl-ink: #eef0f3; --hl-accent: #7c8cff; }

Remember that this only works for inline SVG. An <img> cannot see your page's variables. More in Dark mode illustrations and How to recolour SVG illustrations.

Performance and accessibility

  • Always set width and height (or an aspect ratio) to avoid layout shift while the image loads.
  • Lazy-load illustrations below the fold with loading="lazy".
  • Optimise files with SVGO, but keep viewBox and any class names or variables the theming depends on.
  • Decorative? Use alt="" or aria-hidden="true". Meaningful? Give it role="img" and an aria-label.
  • Do not hot-link illustrations from a third-party URL in production. Save them into your project.

Questions

Should I use img or inline SVG in React?

Use img for static images that never change colour. Use inline SVG (directly, via SVGR, or via a component library) when you need dark mode, brand colours or animation, because only inline SVG can be styled by your CSS.

Does next/image optimise SVG files?

No. SVGs are served as they are, so a plain img tag works just as well for SVG files in Next.js.

How do I change SVG colours in React?

Render the SVG inline and drive its colours with currentColor or CSS variables, or use a component that exposes colour props.

Can I install illustrations with the shadcn CLI?

Yes, if the library publishes a registry. Liniqo free illustrations install with npx shadcn@latest add @liniqo/ followed by the illustration id, after adding the registry to components.json.

Keep reading