Adding Custom Icons to Links

This page demonstrates how to use the wrapperTemplate option of rehype-smart-links to add custom icons for different types of links.

SVG Icon Examples

Using SVG icons to add visual cues for different types of links:

Beautify Links with SVG Icons

Internal Link

External Link

Broken Link

JS
// astro.config.mjs
import { defineConfig } from 'astro/config';
import rehypeSmartLinks from 'rehype-smart-links';
import { h } from 'hastscript';

export default defineConfig({
  markdown: {
    rehypePlugins: [[
      rehypeSmartLinks, 
      {
        wrapperTemplate: (node, linkType) => {
          let icon;
          
          if (linkType === 'internal') {
            // Internal link icon - chain icon
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-internal'
            }, [
              h('path', { d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' }),
              h('path', { d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' })
            ]);
            
            // Add to the end of the link
            node.children.push(icon);
          } 
          else if (linkType === 'external') {
            // External link icon - external arrow
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-external'
            }, [
              h('path', { d: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6' }),
              h('polyline', { points: '15 3 21 3 21 9' }),
              h('line', { x1: '10', y1: '14', x2: '21', y2: '3' })
            ]);
            
            // Add to the end of the link
            node.children.push(icon);
          } 
          else if (linkType === 'broken') {
            // Broken link icon - broken chain
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-broken'
            }, [
              h('path', { d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' }),
              h('path', { d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' }),
              h('line', { x1: '2', y1: '2', x2: '22', y2: '22' })
            ]);
            
            // Add to the beginning of the link
            node.children = [icon, ...node.children];
          }
          
          // Add Flex layout styles
          if (icon) {
            node.properties.className = [
              ...(node.properties.className || []),
              'flex',
              'items-center',
              'gap-1'
            ];
          }
          
          return node;
        }
      }
    ]]
  }
});

Icon Animation Effects

Add animation effects to icons to make links more interactive:

Icon Animation Effects

Internal Link

External Link

Broken Link

JS
// astro.config.mjs
import { defineConfig } from 'astro/config';
import rehypeSmartLinks from 'rehype-smart-links';
import { h } from 'hastscript';

export default defineConfig({
  markdown: {
    rehypePlugins: [[
      rehypeSmartLinks, 
      {
        wrapperTemplate: (node, linkType) => {
          let icon;
          
          if (linkType === 'internal') {
            // Internal link - arrow icon
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-animate group-hover:scale-125 transition-transform'
            }, [
              h('path', { d: 'M5 12h14' }),
              h('path', { d: 'M12 5L19 12L12 19' })
            ]);
          } 
          else if (linkType === 'external') {
            // External link - diagonal arrow
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-animate group-hover:-translate-y-1 group-hover:translate-x-1 transition-transform'
            }, [
              h('line', { x1: '7', y1: '17', x2: '17', y2: '7' }),
              h('polyline', { points: '7 7 17 7 17 17' })
            ]);
          } 
          else if (linkType === 'broken') {
            // Broken link - warning icon
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round',
              class: 'icon-animate group-hover:rotate-12 transition-transform text-red-500'
            }, [
              h('circle', { cx: '12', cy: '12', r: '10' }),
              h('line', { x1: '12', y1: '8', x2: '12', y2: '12' }),
              h('line', { x1: '12', y1: '16', x2: '12.01', y2: '16' })
            ]);
          }
          
          // Add to node and set style classes
          if (icon) {
            if (linkType === 'broken') {
              node.children = [icon, ...node.children];
            } else {
              node.children.push(icon);
            }
            
            node.properties.className = [
              ...(node.properties.className || []),
              'flex',
              'items-center',
              'gap-1',
              'group'  // For hover state management
            ];
          }
          
          return node;
        }
      }
    ]]
  }
});

Using Icon Libraries

You can also integrate Font Awesome or other icon libraries:

Using Font Awesome Icons

Internal Link

External Link

Broken Link

JS
// astro.config.mjs
import { defineConfig } from 'astro/config';
import rehypeSmartLinks from 'rehype-smart-links';
import { h } from 'hastscript';

export default defineConfig({
  markdown: {
    rehypePlugins: [[
      rehypeSmartLinks, 
      {
        wrapperTemplate: (node, linkType) => {
          let iconClass;
          
          if (linkType === 'internal') {
            iconClass = 'fa fa-book';  // Document icon
          } else if (linkType === 'external') {
            iconClass = 'fa fa-external-link';  // External link icon
          } else if (linkType === 'broken') {
            iconClass = 'fa fa-exclamation-triangle';  // Warning icon
          }
          
          if (iconClass) {
            const icon = h('i', { class: iconClass });
            
            // Decide icon position based on link type
            if (linkType === 'broken') {
              node.children = [icon, ...node.children];
            } else {
              node.children.push(icon);
            }
            
            // Add flex layout
            node.properties.className = [
              ...(node.properties.className || []),
              'flex',
              'items-center',
              'gap-2'
            ];
          }
          
          return node;
        }
      }
    ]]
  }
});

Dynamically Select Icons Based on Site

You can dynamically select different icons based on the link destination URL:

Select Icons Based on Link Destination

Internal Link

External Link

Broken Link

JS
// astro.config.mjs
import { defineConfig } from 'astro/config';
import rehypeSmartLinks from 'rehype-smart-links';
import { h } from 'hastscript';

export default defineConfig({
  markdown: {
    rehypePlugins: [[
      rehypeSmartLinks, 
      {
        wrapperTemplate: (node, linkType, url) => {
          let icon;
          
          if (linkType === 'internal') {
            // Select icon based on URL path
            if (url.includes('/docs/')) {
              // Documentation page icon
              icon = h('svg', {
                xmlns: 'http://www.w3.org/2000/svg',
                width: '16',
                height: '16',
                viewBox: '0 0 24 24',
                fill: 'none',
                stroke: 'currentColor',
                'stroke-width': '2',
                'stroke-linecap': 'round',
                'stroke-linejoin': 'round'
              }, [
                h('path', { d: 'M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z' }),
                h('path', { d: 'M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z' })
              ]);
            }
          } 
          else if (linkType === 'external') {
            // Select specific icon based on external site
            if (url.includes('github.com')) {
              // GitHub icon
              icon = h('svg', {
                xmlns: 'http://www.w3.org/2000/svg',
                width: '16',
                height: '16',
                viewBox: '0 0 24 24',
                fill: 'none',
                stroke: 'currentColor',
                'stroke-width': '2',
                'stroke-linecap': 'round',
                'stroke-linejoin': 'round'
              }, [
                h('path', { d: 'M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22' })
              ]);
            }
          } 
          else if (linkType === 'broken') {
            // Broken link icon
            icon = h('svg', {
              xmlns: 'http://www.w3.org/2000/svg',
              width: '16',
              height: '16',
              viewBox: '0 0 24 24',
              fill: 'none',
              stroke: 'currentColor',
              'stroke-width': '2',
              'stroke-linecap': 'round',
              'stroke-linejoin': 'round'
            }, [
              h('path', { d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z' }),
              h('path', { d: 'M13 2v7h7' }),
              h('line', { x1: '8', y1: '17', x2: '16', y2: '17' }),
              h('line', { x1: '8', y1: '13', x2: '14', y2: '13' }),
              h('line', { x1: '8', y1: '9', x2: '10', y2: '9' }),
              h('line', { x1: '4', y1: '22', x2: '20', y2: '2' })
            ]);
          }
          
          // Add icon and styles
          if (icon) {
            if (linkType === 'broken') {
              node.children = [icon, ...node.children];
            } else {
              node.children.push(icon);
            }
            
            node.properties.className = [
              ...(node.properties.className || []),
              'flex',
              'items-center',
              'gap-2'
            ];
          }
          
          return node;
        }
      }
    ]]
  }
});

Next Steps

Explore more examples:

Related Links Astro Tailwind CSS DaisyUI
Resources GitHub NPM