Twig Icon Function
When to Use
You need to render icons in Twig templates with caching and settings support.
Decision
There is exactly one signature. IconsTwigExtension::getIconRenderable() takes the pack and the icon as two separate arguments:
public function getIconRenderable(?string $pack_id, ?string $icon_id, ?array $settings = []): array
| Call | Result |
|---|---|
icon('my_theme', 'home') |
Correct. Settings default to [] |
icon('my_theme', 'home', {size: 32}) |
Correct, with settings |
icon(pack_var, icon_var, settings) |
Correct — the arguments are ordinary values |
icon('my_theme:home') |
Fatal ArgumentCountError — $icon_id has no default |
icon('my_theme:home', {size: 32}) |
Fatal TypeError — an array is passed into ?string $icon_id, and the file is declare(strict_types=1) |
The combined pack:id string is the identifier used by PHP APIs (IconPackManager::getIcon(), IconDefinition::getRenderable(), the UI Icons field's target_id). It has never been accepted by the Twig function: IconsTwigExtension.php has one commit in its entire history — the one that created it, ed6e929 for issue #3471494 — and the signature there is byte-identical to the one in 11.3.11. The single-string form was never valid in any Drupal release; it is an error, not a deprecation.
When either ID is empty or NULL, getIconRenderable() returns [] — so a missing icon renders nothing and raises nothing. Only the wrong arity is fatal.
Pattern
Basic icon rendering:
{# Simple icon #}
{{ icon('my_theme', 'home') }}
{# Icon with size #}
{{ icon('my_theme', 'home', { size: 32 }) }}
{# Icon with multiple settings #}
{{ icon('my_theme', 'home', {
size: 32,
color: '#007bff',
class: 'me-2'
}) }}
{# Conditional icon #}
{% if user.isAuthenticated %}
{{ icon('my_theme', 'user', { size: 24 }) }}
{% else %}
{{ icon('my_theme', 'user-guest', { size: 24 }) }}
{% endif %}
{# Dynamic icon ID #}
{% set status_icons = {
'success': 'check-circle',
'error': 'x-circle',
'warning': 'alert-circle'
} %}
{{ icon('my_theme', status_icons[status], { size: 20 }) }}
Splitting a stored pack:id string (icon field values, config, imported data):
{% set parts = full_id|split(':', 2) %}
{{ icon(parts[0], parts[1], { size: 24 }) }}
Semantic icons with labels. decorative and aria_label are not core settings — they only do anything if your pack template reads them:
<a href="/search">
{{ icon('my_theme', 'search', {
size: 20,
decorative: false,
aria_label: 'Search'
}) }}
<span class="visually-hidden">Search</span>
</a>
Reference: /core/lib/Drupal/Core/Template/IconsTwigExtension.php for the icon() Twig function definition.
Common Mistakes
icon('pack:id')oricon('pack:id', {…})→ Fatal, on every Drupal 11 release. Split into two arguments- Assuming the render array carries cache metadata → It does not.
getIconRenderable()returns#type/#pack_id/#icon_id/#settingsand nothing else, andpreRenderIcon()adds no#cache - Expecting an error when the icon does not exist → You get an empty render array and blank output; check
/admin/appearance/ui/iconsorgetIcons()instead - Passing a setting the pack template does not print → Silently ignored; settings are just context keys
- Not providing text alternatives → Decorative icons need
aria-hidden, semantic icons need labels