Enhancing Your Django Apps: Inline SVG Icons with Iconify and Tailwind

·

4 min read

When building a web application, we often face the challenge of managing a diverse set of icons. Icons are integral to user interface design. However, managing these icons can quickly become cumbersome, especially when aiming for consistency and efficiency across different parts of the application.

Enter Iconify, a solution that streamlines the use of icons in web projects. Iconify offers a vast library of icons from various collections, simplifying their integration into projects. It enables dynamic generation of SVG icons, eliminating the need to download and store them manually. Through the use of APIs or client-side libraries, Iconify fetches and renders SVG icons on-demand based on the icons' identifiers.

While icons can be added to HTML through CSS or directly as SVG, using inline SVG provides greater flexibility. This method allows for complex interactions and styling, making icons more accessible and adaptable to the application's state, thereby improving user experience.

This article will show you how to set up Iconify along with TailwindCSS so that you can easily manage your icons. It assumed that you have set the tailwindcss node package and that you use this to generate your CSS file.

The Goal

If you are using the CSS method, you can easily use Iconify as follows.

<span class="icon-[mdi-light--home]"></span>
<span class="icon-[flowbite--archive-solid"></span>

To achieve this, install the @iconify/tailwind package, then open, import from iconify/tailwind, and add it to the list of plugins. Example:

const { addDynamicIconSelectors } = require('@iconify/tailwind');

/** @type {import('tailwindcss').Config} */
module.exports = {
   content: ['./src/*.html'],
   plugins: [
       require('@iconify-json/flowbite'),
       require('@iconify-json/mdi'),
       addDynamicIconSelectors(),
   ],
};

However, in this article, we will use the inline method. Essentially, what we want to achieve is the following:

<svg xmlns="http://www.w3.org/2000/svg"
     class="w-4 w-4 me-2"
     fill="currentColor"
     viewBox="0 0 20 20">
  {% include "icons/icon-[mdi-light--home]" %}
</svg>

Or, if you use Django Slippers like me, you can define a component.

{# icons.html #}
{% var size=size|match:"extra-small: h-3 w-3 , small: h-3 w-3, base: h-3.5 w-3.5 , large: h-4 w-4, extra-large: h-5 w-5"|default:"h-3.5 w-3.5" %}
{% var color=color|default:"" %}
{% var fill_color=fill_color|default:"currentColor" %}
{% var viewbox=viewbox|default:"0 0 24 24" %}
{% var extra_class=extra_class|default:"" %}

{% var icon_path="icons/"|add:name %}

<svg xmlns="http://www.w3.org/2000/svg"
     class="{{ size }} {{ color }} {{ extra_class }}"
     fill="{{ fill_color }}"
     viewBox="{{ viewbox }}">
  {% include icon_path %}
</svg>

And use it in your templates as follows.

{% icon name="icon-[flowbite--adjustments-horizontal-outline]"  %}
{% icon name="icon-[flowbite--face-grin-stars-outline]"  %}
{% icon name="icon-[flowbite--archive-solid]" size="small" %}
{% icon name="icon-[flowbite--annotation-solid]" %}

Install Necessary Packages

Locate your tailwind.config.js file and write the following functions.

import { readFileSync, mkdir, writeFileSync } from 'fs';
import { getIconData, matchIconName } from '@iconify/utils';

const TARGET = 'reux/templates/icons';
const CACHE = Object.create(null);


function loadIconSet(prefix) {
  let main = require.resolve(`@iconify-json/${prefix}/icons.json`);
  if (!main) {
    return;
  }

  if (CACHE[main]) {
    return CACHE[main];
  }

  try {
    const result = JSON.parse(readFileSync(main, 'utf8'));
    CACHE[main] = result;
    return result;
  }
  catch { }
}


function addDynamicIconSelectors(options) {
  const prefix = 'icon';
  const outDir = `${TARGET}`;

  mkdir(outDir, { recursive: true }, (err) => { if (err) throw err; });

  return (({ matchComponents }) => {
    matchComponents({
      [prefix]: (icon) => {
        // Get icon identifier
        const nameParts = icon.split(/--|\:/);

        if (nameParts.length !== 2) { 
            throw new Error(`Invalid icon name: "${icon}"`); 
        }

        // Get icon set prefix and icon name
        const [iconSetPrefix, name] = nameParts;
        if (!(iconSetPrefix.match(matchIconName) && name.match(matchIconName))) {
          throw new Error(`Invalid icon name: "${icon}"`);
        }

        // Load icon set
        const iconSet = loadIconSet(iconSetPrefix);
        if (!iconSet) {
          throw new Error(
              `Cannot load icon set for "${prefix}".
               Install "@iconify-json/${prefix}" as dev dependency?`
          );
        }

        // Get icon data
        const data =  getIconData(iconSet, name);

        // Save data to file
        writeFileSync(`${__dirname}/${outDir}/${prefix}-[${icon}]`, 
                      data['body']);
        return;
     }
    });
  })
}


/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
      './reux/templates/**/*.html',
  ],
  theme: {
    extend: {},
  },
  plugins: [
      require('flowbite/plugin'),
      require('@iconify-json/flowbite'),
      require('@iconify-json/mdi'),
      addDynamicIconSelectors(),
  ],
}

The addDynamicIconSelectors will scan the template files, and when it finds any mention of the text that matches our icon identifier (e.g., icon-[flowbite--annotation-solid]), it will read the icon set, get the SVG path data, and save it to the file, which we can then include using Django Template Language.

Conclusion

Incorporating inline SVG icons into our web application with Iconify and Tailwind CSS offers a powerful way to enhance the project's visual appeal and user interaction. By following the steps outlined above, you can easily manage and use a vast library of icons, providing a more flexible and accessible experience.