Render icons in themes
Use Easy SVG icons in classic and block themes: easy_svg_icon() in templates, the shortcode, the Icon block, accessibility, filters, caching and colours.
Tutorial · Easy SVG
Easy SVG keeps a library of sanitized SVG icons on your site. This tutorial shows the three ways to put one on a page, how to make each one accessible, how to size and colour icons with CSS, and how to change their markup with filters. Every snippet works against Easy SVG 5.4.
easy_svg_icon()
PHP templates, any WordPress version.
[easy_svg_icon]
Content where nobody writes PHP.
Icon block
The editor, WordPress 7.1 and newer.
Filters
Change the markup on the way out.
Before you start
Switch the library on
Go to Icons, Settings and turn on the icon library. The template function works without the switch, but the shortcode and the Icon block collection are only registered while it is on. The switch takes effect from the next request.
Add icons
Under Icons, Icon library, give an icon a name and choose an SVG, or pick SVGs from the media library. Every file goes through the site's sanitizer, the size limit and a hardening pass before it is stored.
Note the slug
Each icon has a slug derived from its name: Arrow left becomes arrow-left. If the name was taken, WordPress appends -2. The copy button on each icon gives you the exact PHP, shortcode and block form.
Which method fits
| Where | Method | WordPress | Draws |
|---|---|---|---|
PHP template, render.php | easy_svg_icon() | 6.6 and newer | the stored file |
| Post content, widgets, builders | [easy_svg_icon] | 6.6 and newer | the stored file |
| Block editor, patterns, block themes | Icon block, collection "Easy SVG" | 7.1 and newer | a registry copy (paths and polygons) |
| PHP, same look as the block | wp_get_icon( 'easy-svg/slug' ) | 7.1 and newer | the registry copy |
The difference in the last column matters: the Icon block and wp_get_icon() draw what WordPress's icon registry keeps, which is only paths and polygons. Easy SVG converts circles, rectangles, lines and outline strokes so most icons still look right, but gradients, masks, text and title elements do not survive there. easy_svg_icon() and the shortcode print the stored file unchanged.
Icons in PHP templates
easy_svg_icon() returns the markup; echo it. Pass a bare slug or easy-svg/slug.
<a class="site-header__search" href="<?php echo esc_url( home_url( '/?s=' ) ); ?>">
<?php echo easy_svg_icon( 'search', array( 'class' => 'icon' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<span><?php esc_html_e( 'Search', 'mytheme' ); ?></span>
</a>
Do not wrap the result in esc_html() or wp_kses_post(): both destroy SVG. The markup is safe to print because it was sanitized and hardened when it was stored.
A theme should not break when the plugin is missing or an icon was deleted. Wrap the call once:
/**
* Prints an icon from Easy SVG, or nothing.
*
* @param string $name Icon slug or easy-svg/slug.
* @param array $args class, label, decorative.
*/
function mytheme_the_icon( $name, $args = array() ) {
if ( ! function_exists( 'easy_svg_icon' ) ) {
return;
}
echo easy_svg_icon( $name, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
Icons in content: the shortcode
[easy_svg_icon name="easy-svg/heart" class="icon" label="Favourite"]
It takes name (required), class and label, and renders exactly what easy_svg_icon() renders. While the icon library is switched off, the shortcode is not registered and shows as plain text.
If your theme renders a custom field that may contain the shortcode, call do_shortcode() last, after any escaping, or the SVG is stripped:
$note = wp_kses_post( get_post_meta( get_the_ID(), 'mytheme_note', true ) );
echo do_shortcode( $note ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
Icons in the block editor
On WordPress 7.1 and newer, add an Icon block and choose the "Easy SVG" collection in its picker. The collection appears as soon as the library holds one valid icon. In a pattern or a block template, write:
<!-- wp:icon {"icon":"easy-svg/arrow-right"} /-->
In PHP, wp_get_icon( 'easy-svg/arrow-right', array( 'size' => 20 ) ) gives you the same drawing the block shows. Note that core sets width and height to 24 pixels unless you pass another size, or null to keep the file's own size.
If an icon is deleted, Icon blocks that used it show a notice in the editor and draw nothing on the page.
Accessibility
Every icon is either decoration or content. Decide per use, not per icon.
| You want | easy_svg_icon() arguments | Shortcode | Output on the svg root |
|---|---|---|---|
| Decoration next to text | none | no label | aria-hidden="true" focusable="false" |
| The only content of a link or button | label | label="..." | role="img" focusable="false" aria-label="..." |
| Meaningful, named after the library title | decorative false | not possible | role="img" and aria-label with the stored title |
| Hidden, although a label exists | decorative true | not possible | aria-hidden="true" focusable="false" |
Rules of thumb:
- If the visible text next to the icon already says it, the icon is decorative. A label would make screen readers say it twice.
- An icon-only button or link needs a label. Translate it:
__( 'Close', 'mytheme' ). - Easy SVG never prints
role="img"without a name. When you mark an icon meaningful without a label, it falls back to the stored title, then to the slug read as words. focusable="false"keeps old Internet Explorer and Edge from making the SVG a tab stop.- A
titleelement inside the SVG shows a tooltip on hover, but do not rely on it as the accessible name. Uselabel. You can add atitlewith theeasy_svg_icon_markupfilter (example below); it does not reach the Icon block.
Size and colour with CSS
Easy SVG adds no width or height to templates or the shortcode. An SVG with only a viewBox grows to fill its container, so give your icon class a size:
.icon {
display: inline-block;
width: 1em;
height: 1em;
vertical-align: -0.125em;
flex-shrink: 0;
}
For the colour to follow the text, the SVG must not hard-code it. Two cases:
- Filled icons without their own
filltake the colour from CSS: addfill: currentColor;to.icon. Icons withfill="#000"on their paths keep black; clean the file, or remove the attribute with a filter. - Outline icons (Feather, Lucide, Tabler, Heroicons outline) usually use
stroke="currentColor"andfill="none". In templates they follow the text colour as they are. In the Icon block Easy SVG carries the stroke as classes, and its stroke stylesheet setsstroke: currentColorwith the original width (1, 1.25, 1.5, 1.75, 2, 2.5 or 3; other widths fall back to 1). A fixed stroke colour also becomescurrentColorthere.
To change the stroke width of outline icons in templates, target the shapes, because the width is written on them:
.icon--bold path,
.icon--bold circle,
.icon--bold line,
.icon--bold polyline,
.icon--bold rect {
stroke-width: 2.5;
}
Changing the markup with filters
Two filters, two render paths:
| Filter | Affects | Runs |
|---|---|---|
easy_svg_icon_markup | easy_svg_icon() and the shortcode | once per rendered icon |
easy_svg_registrable_icons | Icon block and wp_get_icon() | once per request, on init |
Add a tooltip title to one icon in templates:
add_filter( 'easy_svg_icon_markup', 'mytheme_icon_tooltip', 10, 3 );
function mytheme_icon_tooltip( $markup, $slug, $collection ) {
if ( 'easy-svg' !== $collection || 'info' !== $slug || '' === $markup ) {
return $markup;
}
$title = '<title>' . esc_html__( 'More information', 'mytheme' ) . '</title>';
return preg_replace( '/(<svg\b[^>]*>)/i', '$1' . $title, $markup, 1 );
}
Hide internal icons from the editor's picker, while templates keep using them:
add_filter( 'easy_svg_registrable_icons', 'mytheme_hide_internal_icons' );
function mytheme_hide_internal_icons( $icons ) {
return array_values(
array_filter(
(array) $icons,
static function ( $icon ) {
return 0 !== strpos( (string) ( $icon['slug'] ?? '' ), 'internal-' );
}
)
);
}
Both filters print what you return without escaping. Never return markup that came from a request, a user field or a remote server without running it through easy_svg_sanitizer() and easy_svg_harden_icon_markup() first. If you build a file path from $slug, validate it against EASY_SVG_ICON_NAME_PATTERN: in a shortcode the slug is written by whoever edits the post.
Caching
You rarely need to think about it, but it helps to know what is cached:
- Templates and shortcode. Each call looks the icon up with
get_page_by_path(). WordPress keeps that answer in the object cache until any post changes, so the same icon twice on a page costs one query. With a persistent object cache it also survives between requests. Youreasy_svg_icon_markupcallbacks are not cached; cache expensive work yourself. - Icon block. The list of icons handed to WordPress is cached for a day in the transient
easy_svg_icons, tied to the optioneasy_svg_icons_version. Adding, replacing, renaming or deleting an icon through the plugin, WP-CLI or an importer raises the version, so the next request is current.easy_svg_registrable_iconsruns on every request on top of that cache. - Page caches. A full-page cache (plugin or CDN) still serves old HTML after you replace an icon. Purge it as you would after any content change.
Checklist
- The library is switched on if you use the shortcode or the Icon block.
- Template calls are wrapped in
function_exists( 'easy_svg_icon' ). - Icon-only links and buttons have a translated
label; icons next to text have none. - Your icon class sets
width,heightand, for filled icons,fill: currentColor. - Nothing escapes the icon markup after it is rendered.
- Filters return the original markup for every icon they do not handle.