A decade ago, Chris Coyier from CSS-Tricks described ways to consume SVG, which can be distilled into two groups: to link and to embed.
Fast forward to 2022, the market has shifted from WordPress+PHP to React+TypeScript, but the same dilemma “to link or to embed?” still applies because, at the bottom of it, we’re still dealing with the same HTML and CSS as we did a decade ago.
So what’s the best way to import SVG in MDX?
The answer depends. Do you want to style a given SVG differently per dark/light theme? Also, do you use any raw <text>
with custom fonts inside SVG?
❔ Native markdown ways
Native markdown notation can import images (including SVG) like this:
![Various layouts on codsen.com website](/images/codsen-article-017-layouts.svg)
In Remix, you’d put the SVG file into /public/images/
, and it would become available in Remix under the /images/*
path.
This approach does work, but you don’t inherit any CSS from the upper scope:
- it’s impossible to tap into
class="dark"
at the root (in my case,remix-themes
sets the current theme’s class on<html>
— if<html class="">
makes you cringe too, I already raised a GH ticket to move todata-*
attribute — butremix-themes
is still a great plugin) - not possible to reference the CSS variables inside the SVG file
- also impossible for SVG
<text>
elements to inherit the text styles (relevant considering custom fonts and dark/light themes)
Theoretically, the benefits would be: browser-side caching per file, which is DRY.
❌ Importing SVG directly in MDX
Don’t import SVG files directly in MDX:
import * as Layouts from "../../../public/images/codsen-article-017-layouts.svg";
...
<Layouts/>
or
import Layouts from "../../../public/images/codsen-article-017-layouts.svg";
...
<Layouts/>
It won’t work in Remix.
✅ A dedicated TSX component
The most flexible solution is to create a .tsx
component for each SVG. It would end up bundled (so, an “embed” approach) and Remix would pre-fetch it along everything else. In MDX, you’d import it as normal:
import { Article017Layouts } from "~/components/svg/017-layouts.tsx";
But before I show the component’s code — the accessibility...
Accessibility
Use SVGR converter to convert SVG into TSX. There might be a few bits missing.
The first one is <title>
. MDN describes the solution quite vaguely (as always), but the gist of it is to use <title>
+<desc>
and to nest it inside <svg>
and it must be the first nested child element for backwards compatibility with SVG 1.1.
The aria-labelledby
is normally used to link the element to the describing elements, but we can go belt-and-braces and point the aria-labelledby
to <title>
by its id
:
// app/components/svg/017-layouts.tsx
import type { SVGProps } from "react";
export const Article017Layouts = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 738 581.98"
aria-labelledby="codsen-article-017-layouts"
role="img"
{...props}
>
<title id="codsen-article-017-layouts">
Various layouts on codsen.com website
</title>
<desc>
Codsen.com website uses various layouts: absolutely centred-one like on the root home page, two-column like at Open Source root, styled with background images like in login page, also different arrangements of the sidebar, both left and right.
</desc>
...
Also, add role="img"
(MDN reference).
Theoretically, aria-label
could be used instead of <title>
but TPGi and many other trusted sources use the <title>
.
With these measures in place, we should be solid accessibility-wise.
Takeaway
I prefer to create TSX components for each SVG and bundle them up inline. It allows the most freedom to style them via CSS: from dark/light theming to SVG inheriting font styling, so SVG <text>
elements look consistent with the rest of the page.