Making SVG Accessible in Next.js with Typescript and MDX

Making SVG Accessible in Next.js with Typescript and MDX

Introduction

Before svgMagic

example.tsx
import React from 'react';

const ExampleComponent = () => (
<div>
<svg className="h-8 w-8 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 193.65 211.87">
     <path fill="currentColor" d="M187.66,74.62l-5.07-5.76h0s.44-7.58.44-7.58c0-.32-.24-.7-.53-.85s-.74-.11-1,.09l-5.76,4.95h0s-7.66-.54-7.66-.54c-.32,0-.7.23-.84.52-.14.29-.1.73.1.99l5.07,5.76h0s-.44,7.58-.44,7.58c0,.32.24.7.53.85s.74.1,1-.09l5.75-4.94h0s.02,0,.02,0l7.65.54c.32,0,.7-.23.85-.52.14-.29.1-.73-.1-.99Z"/>
     <path fill="currentColor" d="M156.77,41.25c-.07.73.37,1.64.99,2.04,0,0,0,0,0,0h0s0,0,0,0c.62.4,1.62.42,2.25.04l14.08-9.65.1-.06h.08s17.03,3.11,17.03,3.11c.73.08,1.63-.35,2.03-.95h.02s0-.01,0-.01c.39-.62.39-1.61.02-2.24l-9.96-14.16-.04-.06v-.12s2.86-16.83,2.86-16.83c.07-.73-.37-1.63-.99-2.02h0s0,0,0,0c0,0,0,0,0,0-.62-.4-1.64-.42-2.27-.05l-14.17,9.72-17.11-3.11c-.73-.08-1.64.35-2.03.96s-.4,1.62-.02,2.25l10.01,14.23-2.87,16.94Z"/>
     <path fill="currentColor" d="M75.98,24.75l9.45,4.43,3.56,9.68c.18.4.68.75,1.12.77s.98-.27,1.19-.66l4.49-9.28,9.84-3.48c.4-.17.75-.67.77-1.1s-.27-.96-.66-1.17l-9.45-4.42-3.56-9.68c-.18-.4-.68-.74-1.12-.76s-.98.26-1.19.65l-4.49,9.29-9.84,3.48c-.4.18-.75.67-.77,1.1s.27.97.66,1.17Z"/>
     <path fill="currentColor" d="M116.59,6.52l3.14,4.67-1.04,5.46c-.03.24.11.53.31.67s.53.15.73.03l4.65-3.05,5.52,1.12c.23.03.53-.1.66-.3s.14-.52.02-.73l-3.14-4.67,1.04-5.47c.03-.24-.11-.53-.31-.66s-.53-.15-.73-.03l-4.66,3.05-5.52-1.12c-.24-.03-.53.1-.66.3s-.14.52-.02.73Z"/>
     <path fill="currentColor" d="M123.76,83.69l-14.09-10.66c-1.94-1.47-4.72-1.08-6.19.86l-10.66,14.09c-1.47,1.94-1.08,4.72.86,6.19l14.09,10.66c1.94,1.47,4.72,1.08,6.19-.86l10.66-14.09c1.47-1.94,1.08-4.72-.86-6.19Z"/>
     <path fill="currentColor" d="M175.74,117.48c-6.56-4.96-15.42-4.59-21.58.29l-24.29-18.38,24.09-31.32c5.2-6.75,3.86-16.35-2.98-21.43l-12.38-9.2c-6.84-5.08-16.61-3.72-21.8,3.03l-23.84,30.99-24.33-18.4c3.02-7.25.96-15.89-5.6-20.85-7.78-5.89-18.86-4.35-24.75,3.43-5.89,7.78-4.35,18.86,3.43,24.75,6.56,4.96,15.42,4.59,21.58-.29l17.5,13.24c-15.2-1.63-30.72,2.34-43.28,11.56l-5.6-4.24c-5.83-4.42-14.15-3.26-18.56,2.57l-10.66,14.09c-4.41,5.84-3.26,14.15,2.57,18.56l14.09,10.66c5.84,4.41,14.15,3.26,18.56-2.57l10.66-14.09c4.42-5.84,3.26-14.15-2.57-18.56l-1.06-.8c11.62-7.64,25.78-10.37,39.3-7.74L10.86,178.16c-5.2,6.76-3.86,16.35,2.98,21.43l12.39,9.2c6.84,5.08,16.6,3.72,21.8-3.03l73.17-95.12c6.28,12.34,7.55,26.78,3.33,40.11l-1.06-.8c-5.84-4.41-14.15-3.26-18.56,2.57l-10.66,14.09c-4.41,5.84-3.26,14.15,2.57,18.56l14.09,10.66c5.83,4.41,14.15,3.26,18.56-2.57l10.66-14.09c4.42-5.84,3.26-14.15-2.57-18.56l-5.6-4.24c5.45-14.6,5.06-30.62-.65-44.8l17.5,13.24c-3.02,7.25-.96,15.89,5.6,20.85,7.78,5.89,18.86,4.35,24.75-3.43,5.89-7.78,4.35-18.86-3.43-24.75ZM59.41,51.63c-2.94,3.88-8.49,4.65-12.37,1.72-3.88-2.94-4.65-8.49-1.72-12.37,2.94-3.89,8.49-4.65,12.37-1.72,3.88,2.94,4.65,8.49,1.72,12.37ZM41.52,104.55l-10.66,14.09c-1.47,1.94-4.24,2.33-6.19.86l-14.09-10.66c-1.94-1.47-2.33-4.24-.86-6.19l10.66-14.09c1.47-1.94,4.24-2.33,6.19-.86l14.09,10.66c1.94,1.47,2.33,4.24.86,6.19ZM120.7,109.63c-4.48,5.52-12.54,6.57-18.26,2.25l-14.09-10.66c-5.72-4.32-6.9-12.37-2.8-18.18l8.23-10.95,2.66-3.52c4.41-5.84,12.72-6.99,18.56-2.57l14.09,10.66c5.84,4.41,6.99,12.73,2.57,18.56l-2.66,3.52-8.3,10.89ZM132.25,167.65c1.94,1.47,2.33,4.24.86,6.19l-10.66,14.09c-1.47,1.94-4.24,2.33-6.19.86l-14.09-10.66c-1.94-1.47-2.33-4.24-.86-6.19l10.66-14.09c1.47-1.94,4.24-2.33,6.19-.86l14.09,10.66ZM172.13,136.9c-2.94,3.88-8.49,4.65-12.38,1.72-3.88-2.94-4.65-8.49-1.71-12.37,2.94-3.89,8.49-4.65,12.37-1.72,3.88,2.94,4.65,8.49,1.72,12.37Z"/>
</svg>
</div>
);
            
export default ExampleComponent;

After svgMagic

example.tsx
import React from 'react';
import SVGM from "@/components/svgm/SVGM"; 

const ExampleComponent = () => (
<div>
<SVGM kind="svgm-mark" className="h-8 w-8 text-white" />
</div>
);
            
export default ExampleComponent;

Scalable Vector Graphics. For an overview of the .svg file format, see w3schools.com, adobe.com, or docusarus.io to familiarize yourself, if you'd like. The .svg format is used for vector based images. Since these are "scalable" they can be displayed at any resolution, on any supported device. Because of this versatility, I lean on this format continually. Any time it can be called in, I go for it.

.svg images are mathematically based images that can be scaled infinitely and maintain their quality. Pretty useful stuff in the realm of web design.

The Good, Bad and Ugly of SVG in Next.js

Below are some various examples of the standard ways we interact with .svg files and SVG code currently, with example code blocks outlining the problems with these methods. This will effectively illustrate the problems that svgMagic solves.

SVG in a React Component

Typescript

example.tsx
// React Component Example (TypeScript)
import React from 'react';

const SvgExample: React.FC = () => (
  <svg width="100" height="100" fill="currentColor">
    <circle cx="50" cy="50" r="40" />
  </svg>
);

export default SvgExample;

Cons:

  • Have to create a new Typescript component for each svg, or you could have one Typescript component as an array or switch of them, but you still have to import them and update the list each time.
  • Extra step of copying your raw .svg file contents, or SVG code into a React component, or having to name an import for each .svg file
  • Have to import the component(s) in each document, or
  • Need to maintain a list of inline SVG code in a parent document, or
  • Must specify an import every time a new SVG is added

Pros:

  • Fine for ocassional usage and one-off situations if you don't work with SVG often.
  • Theming works great once you switch fills to currentColor

MDX

example.tsx
// React Component Example (MDX)
import { SvgExample } from './components/SvgExample';

<SvgExample />

Cons:

  • Have to import each component (or a list of them all, which would still need to be updated each time) in mdx-components
  • Adds significant bulk and many lines of code to your .mdx
  • Difficult to read and parse

Pros:

  • Fine for ocassional usage and one-off situations if you don't work with SVG often.
  • Theming works great once you switch fills to currentColor

SVG in an <img> tag

Typescript

example.tsx
// SVG in an <img> tag (TypeScript)
import React from 'react';
import exampleBlackSvg from './example-light.svg';
import exampleWhiteSvg from './example-dark.svg';

const ImgExample: React.FC = () => (
  <img src={exampleWhiteSvg} alt="Example SVG in White on Dark Background" className="hidden dark:flex"/>
  <img src={exampleBlackSvg} alt="Example SVG in Black on Light Background" className="flex dark:hidden"/>
);

export default ImgExample;

Cons:

  • Have to import each .svg you plan to use, this means two imports if you're using theming
  • Theming is "hacky", typically achieved by hiding a Light <img> element on a Light background, and showing it when the theme is changed to dark, and vice versa.

Pros:

  • Fine for ocassional usage and one-off situations if SVG is infrequently used
  • Simplifies usage by treating SVG's as static images

MDX

example.mdx
<img src="/path/to/example.svg" alt="Example SVG" />

Cons:

  • Have to have multiple <img> tags if you want to be accessible and support dark mode, too.
  • Theming is "hacky", typically achieved by hiding a Light <img> element on a Light background, and showing it when the theme is changed to dark, and vice versa.
  • To support theming, multple versions have to be exported from your vector art program, and maintain two or more versions of almost the exact same source file.

Pros:

  • No need for a Typescript parent component
  • No need to define in mdx-components
  • No need to import each .svg directly
  • Fine for ocassional usage and one-off situations if you don't work with SVG often.

SVG Directly Inline

Typescript

example.tsx
<?xml version="1.0" encoding="UTF-8"?>
      <svg className="h-8 w-8 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 193.65 211.87">
           <path fill="currentColor" d="M187.66,74.62l-5.07-5.76h0s.44-7.58.44-7.58c0-.32-.24-.7-.53-.85s-.74-.11-1,.09l-5.76,4.95h0s-7.66-.54-7.66-.54c-.32,0-.7.23-.84.52-.14.29-.1.73.1.99l5.07,5.76h0s-.44,7.58-.44,7.58c0,.32.24.7.53.85s.74.1,1-.09l5.75-4.94h0s.02,0,.02,0l7.65.54c.32,0,.7-.23.85-.52.14-.29.1-.73-.1-.99Z"/>
           <path fill="currentColor" d="M156.77,41.25c-.07.73.37,1.64.99,2.04,0,0,0,0,0,0h0s0,0,0,0c.62.4,1.62.42,2.25.04l14.08-9.65.1-.06h.08s17.03,3.11,17.03,3.11c.73.08,1.63-.35,2.03-.95h.02s0-.01,0-.01c.39-.62.39-1.61.02-2.24l-9.96-14.16-.04-.06v-.12s2.86-16.83,2.86-16.83c.07-.73-.37-1.63-.99-2.02h0s0,0,0,0c0,0,0,0,0,0-.62-.4-1.64-.42-2.27-.05l-14.17,9.72-17.11-3.11c-.73-.08-1.64.35-2.03.96s-.4,1.62-.02,2.25l10.01,14.23-2.87,16.94Z"/>
           <path fill="currentColor" d="M75.98,24.75l9.45,4.43,3.56,9.68c.18.4.68.75,1.12.77s.98-.27,1.19-.66l4.49-9.28,9.84-3.48c.4-.17.75-.67.77-1.1s-.27-.96-.66-1.17l-9.45-4.42-3.56-9.68c-.18-.4-.68-.74-1.12-.76s-.98.26-1.19.65l-4.49,9.29-9.84,3.48c-.4.18-.75.67-.77,1.1s.27.97.66,1.17Z"/>
           <path fill="currentColor" d="M116.59,6.52l3.14,4.67-1.04,5.46c-.03.24.11.53.31.67s.53.15.73.03l4.65-3.05,5.52,1.12c.23.03.53-.1.66-.3s.14-.52.02-.73l-3.14-4.67,1.04-5.47c.03-.24-.11-.53-.31-.66s-.53-.15-.73-.03l-4.66,3.05-5.52-1.12c-.24-.03-.53.1-.66.3s-.14.52-.02.73Z"/>
           <path fill="currentColor" d="M123.76,83.69l-14.09-10.66c-1.94-1.47-4.72-1.08-6.19.86l-10.66,14.09c-1.47,1.94-1.08,4.72.86,6.19l14.09,10.66c1.94,1.47,4.72,1.08,6.19-.86l10.66-14.09c1.47-1.94,1.08-4.72-.86-6.19Z"/>
           <path fill="currentColor" d="M175.74,117.48c-6.56-4.96-15.42-4.59-21.58.29l-24.29-18.38,24.09-31.32c5.2-6.75,3.86-16.35-2.98-21.43l-12.38-9.2c-6.84-5.08-16.61-3.72-21.8,3.03l-23.84,30.99-24.33-18.4c3.02-7.25.96-15.89-5.6-20.85-7.78-5.89-18.86-4.35-24.75,3.43-5.89,7.78-4.35,18.86,3.43,24.75,6.56,4.96,15.42,4.59,21.58-.29l17.5,13.24c-15.2-1.63-30.72,2.34-43.28,11.56l-5.6-4.24c-5.83-4.42-14.15-3.26-18.56,2.57l-10.66,14.09c-4.41,5.84-3.26,14.15,2.57,18.56l14.09,10.66c5.84,4.41,14.15,3.26,18.56-2.57l10.66-14.09c4.42-5.84,3.26-14.15-2.57-18.56l-1.06-.8c11.62-7.64,25.78-10.37,39.3-7.74L10.86,178.16c-5.2,6.76-3.86,16.35,2.98,21.43l12.39,9.2c6.84,5.08,16.6,3.72,21.8-3.03l73.17-95.12c6.28,12.34,7.55,26.78,3.33,40.11l-1.06-.8c-5.84-4.41-14.15-3.26-18.56,2.57l-10.66,14.09c-4.41,5.84-3.26,14.15,2.57,18.56l14.09,10.66c5.83,4.41,14.15,3.26,18.56-2.57l10.66-14.09c4.42-5.84,3.26-14.15-2.57-18.56l-5.6-4.24c5.45-14.6,5.06-30.62-.65-44.8l17.5,13.24c-3.02,7.25-.96,15.89,5.6,20.85,7.78,5.89,18.86,4.35,24.75-3.43,5.89-7.78,4.35-18.86-3.43-24.75ZM59.41,51.63c-2.94,3.88-8.49,4.65-12.37,1.72-3.88-2.94-4.65-8.49-1.72-12.37,2.94-3.89,8.49-4.65,12.37-1.72,3.88,2.94,4.65,8.49,1.72,12.37ZM41.52,104.55l-10.66,14.09c-1.47,1.94-4.24,2.33-6.19.86l-14.09-10.66c-1.94-1.47-2.33-4.24-.86-6.19l10.66-14.09c1.47-1.94,4.24-2.33,6.19-.86l14.09,10.66c1.94,1.47,2.33,4.24.86,6.19ZM120.7,109.63c-4.48,5.52-12.54,6.57-18.26,2.25l-14.09-10.66c-5.72-4.32-6.9-12.37-2.8-18.18l8.23-10.95,2.66-3.52c4.41-5.84,12.72-6.99,18.56-2.57l14.09,10.66c5.84,4.41,6.99,12.73,2.57,18.56l-2.66,3.52-8.3,10.89ZM132.25,167.65c1.94,1.47,2.33,4.24.86,6.19l-10.66,14.09c-1.47,1.94-4.24,2.33-6.19.86l-14.09-10.66c-1.94-1.47-2.33-4.24-.86-6.19l10.66-14.09c1.47-1.94,4.24-2.33,6.19-.86l14.09,10.66ZM172.13,136.9c-2.94,3.88-8.49,4.65-12.38,1.72-3.88-2.94-4.65-8.49-1.71-12.37,2.94-3.89,8.49-4.65,12.37-1.72,3.88,2.94,4.65,8.49,1.72,12.37Z"/>
      </svg> />

Cons:

  • Have to import each .svg (or a list of them all pre-defined) in mdx-components
  • Adds significant bulk and many lines of code to your .mdx
  • Difficult to read and parse

Pros:

  • Fine for ocassional usage and one-off situations if you don't work with SVG often.
  • Theming works great once you switch fills to currentColor

MDX

This one doesn't actually require any different code for MDX from Typescript. SVG is SVG in this case because SVGR/webpack is rendering it for us.

Cons:

  • Have to import each .svg (or a list of them all pre-defined) in mdx-components
  • Adds significant bulk and many lines of code to your .mdx
  • Difficult to read and parse

Pros:

  • Fine for ocassional usage and one-off situations if you don't work with SVG often.
  • Theming works great once you switch fills to currentColor

SVG with SVGMagic

.tsx or .mdx
<SVGM kind="svgm-mark" className="h-16 text-blue-600 dark:text-blue-400" />

Typescript

  • Import once, use over and over again

MDX

  • Import one document into mdx-components
  • Extremely readable, maintainable code
<SVGM kind="aws" className="h-32 mx-auto" />