Skip to main content
go-swarm-icons
On this page

Attribute Management

See how the five-layer attribute merge decides which values win on a rendered icon.

Most icons in a project share the same size, stroke width, or base class. Instead of repeating those attributes on every call, set them once and let the five-layer merge handle the rest.

The five-layer merge

When manager.Get() resolves an icon, the IconRenderer merges attributes from five sources. Higher layers override lower ones. The class attribute is concatenated rather than replaced at every layer.

Layer 1 (lowest) Icon's own attributes
(from SVG file parse or Iconify JSON)
e.g. viewBox="0 0 24 24", width="24", height="24"
Layer 2 Global DefaultAttributes
e.g. stroke="currentColor", stroke-width="2", fill="none"
Layer 3 PrefixAttributes for the resolved prefix
e.g. tabler → stroke-width="1.5"
Layer 4 PrefixSuffix for the resolved prefix + icon name suffix
e.g. heroicons + "-solid" → fill="currentColor"
Layer 5 (highest) Caller-provided attributes
e.g. manager.Get("home", map{"class": "icon", "width": "16"})

Configuring each layer

Layer 2 (global defaults):

Go
import swarmicons "github.com/frostybee/go-swarm-icons"
manager, _ := swarmicons.NewConfig().
AddProvider("lucide", lucide.Provider()).
DefaultAttributes(map[string]string{
"fill": "none",
"stroke": "currentColor",
"stroke-width": "2",
}).
Build()

Calling DefaultAttributes() multiple times is additive. Later calls merge into earlier ones.

Layer 3 (per-prefix):

Go
import swarmicons "github.com/frostybee/go-swarm-icons"
manager, _ := swarmicons.NewConfig().
AddProvider("tabler", tablerProvider).
PrefixAttributes("tabler", map[string]string{
"stroke-width": "1.5",
}).
Build()

These attributes apply only to icons resolved from the "tabler" prefix.

Layer 4 (suffix matching):

Go
import swarmicons "github.com/frostybee/go-swarm-icons"
manager, _ := swarmicons.NewConfig().
AddProvider("heroicons", heroiconsProvider).
PrefixSuffix("heroicons", "solid", map[string]string{
"fill": "currentColor",
}).
PrefixSuffix("heroicons", "outline", map[string]string{
"fill": "none",
"stroke": "currentColor",
}).
PrefixSuffix("heroicons", "", map[string]string{
"fill": "currentColor",
}).
Build()

The renderer checks if the icon name ends with -{suffix}. When multiple suffixes match, the longest suffix wins. An empty suffix ("") acts as a catch-all for icons that match no other suffix.

Layer 5 (caller attributes):

Go
icon, _ := manager.Get("home", map[string]string{
"class": "nav-icon",
"id": "home-btn",
})

Merge rules

  • class: concatenated from all layers, space-separated. No deduplication.
  • All other attributes: last layer wins. A value set at layer 4 overrides the same key from layer 3.
  • Empty-string values: skipped entirely. Passing "fill": "" at layer 5 does not clear a fill set at layer 2.

Full example

Go
import (
"fmt"
swarmicons "github.com/frostybee/go-swarm-icons"
"github.com/frostybee/go-swarm-icons/lucide"
)
func main() {
manager, _ := swarmicons.NewConfig().
AddProvider("lucide", lucide.Provider()).
DefaultPrefix("lucide").
DefaultAttributes(map[string]string{
"fill": "none",
"stroke": "currentColor",
"class": "icon",
}).
Build()
icon, _ := manager.Get("home", map[string]string{
"class": "nav-icon",
"id": "home-link",
})
fmt.Println(icon.ToHTML())
// class includes both "icon" (from layer 2) and "nav-icon" (from layer 5)
// fill="none" and stroke="currentColor" from layer 2
// id="home-link" from layer 5
}

ARIA injection

After the five-layer merge, the renderer injects ARIA attributes for accessibility. The behavior depends on which attributes are already present:

Decorative icons (default)

If no aria-label, aria-labelledby, or role attribute is present:

  • aria-hidden="true" is set (unless already present).
  • focusable="false" is set (unless already present).

This marks the icon as decorative and hidden from screen readers.

Labeled icons

If aria-label, aria-labelledby, or role is present:

  • role="img" is set (unless role is already set to another value).
  • aria-hidden and focusable are not injected.

Existing values are preserved

The renderer never overwrites an attribute that is already set. For example:

  • An icon with aria-hidden="false" keeps that value.
  • An icon with role="button" keeps role="button" even when aria-label is present.
  • An icon with focusable="true" keeps focusable="true".

Interaction with Title()

The Title() fluent method prepends a <title> element to the SVG content but does not set aria-label automatically. To make a titled icon accessible, pass aria-label or aria-labelledby as a caller attribute:

Go
icon.Title("Home").Attr(map[string]string{"aria-label": "Home"})
// Result: role="img" is injected, aria-hidden is NOT set.

See also

Edit this page

Last updated: