Skip to content

Custom Icons

This page shows how to use wrapperTemplate to add icons to link types: inline SVG, icon libraries, or icons chosen dynamically from the link destination. Every preview is the real output of that config.

Build SVG icons with hastscript and insert them into the link, applying the default class with meta.className:

SVG Icons

Internal Link

External Link

Broken Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
const icons = {
internal: 'M13 7l5 5m0 0l-5 5m5-5H6',
external: 'M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14',
broken: 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
};
smartLinks({
wrapperTemplate: (node, type, meta) => {
const svg = h('svg', {
className: 'w-4 h-4',
fill: 'none',
stroke: 'currentColor',
viewBox: '0 0 24 24',
xmlns: 'http://www.w3.org/2000/svg',
}, [
h('path', {
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2',
'd': icons[type],
}),
]);
if (type === 'broken') {
node.children.unshift(svg);
} else {
node.children.push(svg);
}
node.properties.className = [
...(node.properties.className || []),
meta.className,
'inline-flex',
'items-center',
'gap-1',
];
return node;
},
}),

Combine group and transition utilities for hover animations:

Icon Animation

Internal Link

External Link

Broken Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
const animations = {
internal: 'transition-transform group-hover:translate-x-1',
external: 'transition-transform group-hover:-translate-y-1 group-hover:translate-x-1',
broken: 'text-red-500 transition-transform group-hover:rotate-12',
};
const icons = {
internal: 'M13 7l5 5m0 0l-5 5m5-5H6',
external: 'M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14',
broken: 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
};
smartLinks({
wrapperTemplate: (node, type, meta) => {
const svg = h('svg', {
className: `w-4 h-4 ${animations[type]}`,
fill: 'none',
stroke: 'currentColor',
viewBox: '0 0 24 24',
xmlns: 'http://www.w3.org/2000/svg',
}, [
h('path', {
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2',
'd': icons[type],
}),
]);
if (type === 'broken') {
node.children.unshift(svg);
} else {
node.children.push(svg);
}
node.properties.className = [
...(node.properties.className || []),
meta.className,
'group',
'inline-flex',
'items-center',
'gap-1',
];
return node;
},
}),

Use an icon library’s class names (the library stylesheet must be loaded):

Font Awesome Icons

Internal Link

External Link

Broken Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
const icons = {
internal: 'fa fa-book',
external: 'fa fa-external-link',
broken: 'fa fa-exclamation-triangle',
};
smartLinks({
wrapperTemplate: (node, type, meta) => {
const icon = h('i', { className: icons[type] });
if (type === 'broken') {
node.children.unshift(icon);
} else {
node.children.push(icon);
}
node.properties.className = [
...(node.properties.className || []),
meta.className,
'inline-flex',
'items-center',
'gap-2',
];
return node;
},
}),

meta.href contains the link target, so you can pick an icon per destination:

Icons Based on Destination

Internal Link

External Link

Broken Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
const githubIcon = {
type: 'element',
tagName: 'svg',
properties: {
className: ['w-4', 'h-4'],
viewBox: '0 0 24 24',
fill: 'currentColor',
xmlns: 'http://www.w3.org/2000/svg',
},
children: [{
type: 'element',
tagName: 'path',
properties: { d: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z' },
children: [],
}],
};
smartLinks({
wrapperTemplate: (node, type, meta) => {
node.properties.className = [...(node.properties.className || []), meta.className];
if (type !== 'external') {
return node;
}
node.properties.className.push('inline-flex', 'items-center', 'gap-2');
if (meta.href.includes('github.com')) {
node.children.push(githubIcon);
} else {
node.children.push(h('svg', {
className: 'w-4 h-4',
fill: 'none',
stroke: 'currentColor',
viewBox: '0 0 24 24',
xmlns: 'http://www.w3.org/2000/svg',
}, [
h('path', {
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2',
'd': 'M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14',
}),
]));
}
return node;
},
}),

Keep exploring: